/*
* KindlegenRunner.java
*
* created: 30.8.2011
* charset: UTF-8
* license: MIT (X11) (See LICENSE file for full license)
*/
package cz.mp.k3bg.core;
import static cz.mp.k3bg.Application.EOL;
import cz.mp.k3bg.log.LoggerManager;
import cz.mp.util.Stopwatch;
import cz.mp.util.StringUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
/**
* Třída {@code KindlegenRunner} slouží pro vykonání programu {@code kindlegen}.
* <p>
* Testováno s {@code kindlegen} ve verzích: 1.1, 1.2, 2.3, 2.5.
* Soubory knihy by měly být kódovány v {@code UTF-8}.
* <p>
* Příklad použití:
* <tt><pre>
* Kindlegen kindlegen = Kindlegen.getKindlegen("kindlegen");
* if (kindlegen != null) {
* KindlegenRunner kindlegenRunner = new KindlegenRunner();
* kindlegenRunner.setKindlegen(kindlegen);
* kindlegenRunner.setOpfFileNamePath("mybook" + file.separator + "book.opf");
* kindlegenRunner.setOutputFileName("mybook.mobi");
* kindlegenRunner.setKindlegenCommandOutputStream(System.out);
* kindlegenRunner.run();
* System.err.println("kindlegen result: " +
* (kindlegenRunner.isInErrorState() ? "ERROR" : "OK"));
* }
* </pre></tt>
*
* @author Martin Pokorný
* @version 0.6.1
*/
public class KindlegenRunner {
private static final boolean DEBUG = false;
private static final Logger logger =
LoggerManager.getLogger(KindlegenRunner.class, DEBUG);
private Kindlegen kindlegen;
/** Příznak zda {@code kindlegen} skončil s chybou
* (řádek obsahuje text "{@code Error}"). */
private boolean inErrorState = false;
/** Doba běhu samotného programu {@code kindlegen}. */
private long runtime = -1;
/** */
private String opfFileNamePath;
/** */
private String outputFileName;
/** Na tento proud se zapisuje výstup programu {@code kindlegen}. */
private OutputStream kindlegenCommandOutputStream;
/** Přítomnost přepínače {@literal -c2}. */
private boolean c2compress = false;
/** Proces OS, který vykonává {@code kindlegen} se zadanými parametry. */
private Process process;
// -----
/**
*
*/
public KindlegenRunner() {
}
/**
*
* @param opfFileNamePath
* @param outputFileName
*/
public KindlegenRunner(String opfFileNamePath, String outputFileName) {
setOpfFileNamePathImpl(opfFileNamePath);
setOutputFileNameImpl(outputFileName);
}
/**
*
* @param opfFileNamePath
* @param outputFileName
*/
public KindlegenRunner(Kindlegen kindlegen,
String opfFileNamePath, String outputFileName) {
setKindlegenImpl(kindlegen);
setOpfFileNamePathImpl(opfFileNamePath);
setOutputFileNameImpl(outputFileName);
}
/**
* Vykoná {@code kindlegen} s parametry.
*
* @return řádky s výstupem programu {@code kindlegen}; viz
* {@linkplain #setKindlegenCommandOutputStream(java.io.OutputStream)}.
* @throws IOException
* @throws IllegalStateException
*/
public String[] run() throws IOException {
logger.info("");
testStateForRun();
// Sem se také zapisuje výstup programu {@code kindlegen}.
List<String> outputLines = new ArrayList<String>();
inErrorState = false;
Stopwatch runtimeStopwatch = new Stopwatch();
runtimeStopwatch.start();
try {
String[] realCommandArray = assembleKindlegenCommandArray();
logger.info("command: " + Arrays.toString(realCommandArray));
String readableCommand = createReadableCommand(realCommandArray);
// (zde možno zapsat readableCommand do skriptu ...)
writeToCommandOut("> " + readableCommand);
process = Runtime.getRuntime().exec(realCommandArray);
InputStream istream = process.getInputStream();
BufferedReader br = new BufferedReader(
new InputStreamReader(istream));
String line;
while ((line = br.readLine()) != null) {
checkErrorState(line);
outputLines.add(line);
logger.fine(line);
writeToCommandOut(line);
}
try {
process.waitFor();
} catch (InterruptedException e) {
setInErrorState(true);
writeToCommandOut(EOL+"Error: InterruptedException!");
}
// if (proc.exitValue() != 0) {} // nejde, protože kindlegen (v1.2) vrátí vždy 0
br.close();
logger.fine("finished!");
} catch (IOException ex) {
logger.warning(ex.toString());
setInErrorState(true);
runtime = runtimeStopwatch.stop();
throw ex;
}
runtime = runtimeStopwatch.stop();
logger.info("kindlegen runtime = " +
runtimeStopwatch.getTimeSec() + " s");
return (String[]) outputLines.toArray(new String[0]);
}
/**
*
* @throws IllegalStateException
* @see #run()
*/
private void testStateForRun() {
if (StringUtils.isBlank(opfFileNamePath)) {
logger.warning("opfFileNamePath is empty!");
throw new IllegalStateException("opfFileNamePath is empty!");
}
if (StringUtils.isBlank(outputFileName)) {
logger.warning("outputFileName is empty!");
throw new IllegalStateException("outputFileName is empty!");
}
if (kindlegen == null) {
logger.warning("kindlegen = null");
throw new IllegalStateException("kindlegen = null");
}
}
/**
* Zastaví vykonávání {@code kindlegen}, pokud běží.
*
* @see #run()
*/
public void stop() {
if (process == null) {
logger.fine("process=null; skip");
}
else {
logger.info("");
process.destroy();
setInErrorState(true);
}
}
/**
*
* @return Doba běhu programu {@code kindlegen}, nebo -1,
* pokud nebyl spuštěn.
*/
public long getRuntime() {
return runtime;
}
/**
*
* @param line
*/
private void checkErrorState(String line) {
if (line == null) {
throw new IllegalStateException("line is null!");
}
if (line.toLowerCase().startsWith("error")) {
setInErrorState(true);
}
}
/**
*
* @return
*/
public boolean isInErrorState() {
return inErrorState;
}
/**
*
*/
private void setInErrorState(boolean inErrorState) {
logger.fine("inErrorState = " + inErrorState);
this.inErrorState = inErrorState;
}
/**
*
* @param line
* @throws IOException
*/
private void writeToCommandOut(String line) throws IOException {
if (kindlegenCommandOutputStream != null) {
kindlegenCommandOutputStream.write(line.trim().getBytes("UTF-8"));
kindlegenCommandOutputStream.write(EOL.getBytes("UTF-8"));
}
}
/**
*
* @param kindlegen
* @throws IllegalArgumentException
*/
public void setKindlegen(Kindlegen kindlegen) {
setKindlegenImpl(kindlegen);
}
/**
*
* @param kindlegen
* @throws IllegalArgumentException
*/
private void setKindlegenImpl(Kindlegen kindlegen) {
if (kindlegen == null) {
throw new IllegalArgumentException("kindlegen = null");
}
this.kindlegen = kindlegen;
}
/**
*
* @return
*/
public Kindlegen getKindlegen() {
return kindlegen;
}
/**
* Sestaví příkaz pro spuštění programu {@code kindlegen} s
* parametry podle této třídy.
*
* @return
*/
private String[] assembleKindlegenCommandArray() {
if (kindlegen == null) {
logger.warning("kindlegen = null");
throw new IllegalArgumentException("kindlegen = null");
}
logger.fine("");
ArrayList<String> cmds = new ArrayList<String>();
cmds.add(kindlegen.getCommand());
if (opfFileNamePath != null && ! opfFileNamePath.isEmpty()) {
if (c2compress) {
cmds.add(Kindlegen.C2_OPT);
}
if (kindlegen.isNoUnicodeVersion()) {
cmds.add(Kindlegen.FORCE_UNICODE_OPT);
}
cmds.add(opfFileNamePath);
if (outputFileName != null && ! outputFileName.isEmpty()) {
cmds.add(Kindlegen.O_OPT);
cmds.add(outputFileName);
}
}
else {
return new String[0];
}
return cmds.toArray(new String[0]);
}
/**
* Části příkazu sloučí do jednoho textu.
* Tento text lze zkopírovat do příkazové řádky, zalogovat, a pod.
*
* @param array
* @return příkaz v podobě, kterou lze předat příkazovému řádku, nebo
* prázdný řetězec
* @see #assembleKindlegenCommandArray()
*/
private static String createReadableCommand(String[] array) {
StringBuilder sb = new StringBuilder();
for (String cmdPart : array) {
if (cmdPart.contains(" ")) {
sb.append("\"").append(cmdPart).append("\"");
}
else {
sb.append(cmdPart);
}
sb.append(" ");
}
return sb.toString();
}
/**
*
* @return
*/
public String getOutputFileName() {
return outputFileName;
}
/**
*
*/
public void setOutputFileName(String outputFileName) {
setOutputFileNameImpl(outputFileName);
}
/**
*
*/
private void setOutputFileNameImpl(String outputFileName) {
if (outputFileName == null) {
throw new IllegalArgumentException("outputFileName=null");
}
logger.config("outputFileName = " + outputFileName);
this.outputFileName = outputFileName;
}
/**
*
* @return
*/
public String getOpfFileNamePath() {
return opfFileNamePath;
}
/**
*
* @param opfFileNamePath
*/
public void setOpfFileNamePath(String opfFileNamePath) {
setOpfFileNamePathImpl(opfFileNamePath);
}
/**
*
* @param opfFileNamePath
*/
private void setOpfFileNamePathImpl(String opfFileNamePath) {
if (opfFileNamePath == null) {
throw new IllegalArgumentException("opfFileNamePath=null");
}
logger.config("opfFileNamePath = " + opfFileNamePath);
this.opfFileNamePath = opfFileNamePath;
}
/**
*
* @return
*/
public boolean isC2compress() {
return c2compress;
}
/**
* Nastavit vyšší kompresi. Odpovídá parametru {@literal -c2}
* programu {@code kindlegen}.
* Způsobí pomalé sestavení.
*
* @param c2compress
*/
public void setC2compress(boolean c2compress) {
this.c2compress = c2compress;
}
/**
*
* @return
*/
public OutputStream getKindlegenCommandOutputStream() {
return kindlegenCommandOutputStream;
}
/**
* Nastaví výstupní proud do něhož se bude průběžně zapisovat
* std výstup programu {@code kindlegen} během vykonávání.
*
* @param kindlegenCommandOutputStream (může být např. System.out)
*/
public void setKindlegenCommandOutputStream(
OutputStream kindlegenCommandOutputStream) {
if (kindlegenCommandOutputStream == null) {
throw new IllegalArgumentException(
"kindlegenCommandOutputStream=null");
}
this.kindlegenCommandOutputStream = kindlegenCommandOutputStream;
}
} // KindlegenRunner