package net.flibusta.converter.impl; import net.flibusta.converter.ConversionException; import net.flibusta.converter.Converter; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.io.Resource; import java.io.File; import java.io.IOException; import java.io.InputStream; public class EpubToMobiConverter implements Converter, ApplicationContextAware { final Logger logger = Logger.getLogger(EpubToMobiConverter.class); private String path2kindlegen; @Override public File convert(File epub) throws ConversionException { String[] cmd = new String[2]; cmd[0] = path2kindlegen; cmd[1] = epub.getAbsolutePath(); try { int exitCode = execProcess(cmd); if (exitCode != 0 && exitCode != 1) { // ok or warning throw new ConversionException("Epub to mobi conversion failed with error code " + exitCode); } } catch (IOException e) { throw new ConversionException("Epub to mobi conversion failed: " + e.getMessage(), e); } String name = FilenameUtils.getBaseName(epub.getName()); return new File(epub.getParent(), name + ".mobi"); } private int execProcess(String[] cmd) throws IOException { final Process process = Runtime.getRuntime().exec(cmd); StreamConsumer stdout = new StreamConsumer(process.getInputStream()); Thread inputReader = new Thread(stdout); inputReader.start(); StreamConsumer stderr = new StreamConsumer(process.getErrorStream()); Thread stdErrReader = new Thread(stderr); stdErrReader.start(); int exitCode = -1; try { exitCode = process.waitFor(); } catch (InterruptedException e) { process.destroy(); inputReader.interrupt(); stdErrReader.interrupt(); } finally { try { inputReader.join(); } catch (InterruptedException e) { // exit } try { stdErrReader.join(); } catch (InterruptedException e) { // exit } } logger.debug("kindlegen stdout: " + stdout.getResult()); String stderrResult = stderr.getResult(); if (stderrResult.length() > 0) { logger.warn("kindlegen stderr: " + stderrResult); } return exitCode; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Resource resource = applicationContext.getResource("WEB-INF/bin/kindlegen"); if (!resource.exists()) { throw new FatalBeanException("Required conversion utility not found: WEB-INF/bin/kindlegen"); } try { this.path2kindlegen = resource.getFile().getAbsolutePath(); String[] cmd = new String[] {"chmod", "a+x", this.path2kindlegen}; int exitCode = execProcess(cmd); if (exitCode != 0) { throw new FatalBeanException("Can not set executable bin on conversion utility " + this.path2kindlegen); } } catch (IOException e) { throw new FatalBeanException("Required conversion utility not found: WEB-INF/bin/kindlegen: " + e.getMessage()); } } private static class StreamConsumer implements Runnable { private final InputStream stream; private StringBuilder logMessage = new StringBuilder(4096); public StreamConsumer(InputStream stream) { this.stream = stream; } @Override public void run() { byte[] buffer = new byte[4096]; int count; try { while ((count = stream.read(buffer)) > 0) { logMessage.append(new String(buffer, 0, count)); if (Thread.interrupted()) { return; } } } catch (IOException e) { throw new RuntimeException("Can't read stdin from kindlegen"); } finally { IOUtils.closeQuietly(stream); } } public String getResult() { return logMessage.toString(); } } }