diff --git a/source/net/filebot/Execute.java b/source/net/filebot/Execute.java new file mode 100644 index 00000000..c87eb279 --- /dev/null +++ b/source/net/filebot/Execute.java @@ -0,0 +1,85 @@ +package net.filebot; + +import static java.nio.charset.StandardCharsets.*; +import static java.util.Arrays.*; +import static java.util.stream.Collectors.*; +import static net.filebot.Logging.*; + +import java.io.File; +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.util.List; + +import net.filebot.util.ByteBufferOutputStream; + +public class Execute { + + private static final int BUFFER_SIZE = 8 * 1024; + + public static CharSequence execute(String... command) throws IOException { + return execute(asList(command), null); + } + + public static CharSequence execute(String[] command, File directory) throws IOException { + return execute(asList(command), directory); + } + + public static CharSequence execute(String[]... command) throws IOException { + return execute(stream(command).flatMap(a -> stream(a)).collect(toList()), null); + } + + public static CharSequence execute(List command, File directory) throws IOException { + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.redirectError(Redirect.INHERIT); + + if (directory != null) { + processBuilder.directory(directory); + } + + Process process = processBuilder.start(); + + try (ByteBufferOutputStream bb = new ByteBufferOutputStream(BUFFER_SIZE)) { + bb.transferFully(process.getInputStream()); + + int exitCode = process.waitFor(); + CharSequence output = UTF_8.decode(bb.getByteBuffer()); + + // DEBUG + debug.finest(format("Execute %s%n%s", command, output)); + + if (exitCode != 0) { + throw new ExecuteException(command, exitCode); + } + + return output; + } catch (InterruptedException e) { + throw new IOException(String.format("%s timed out", command), e); + } + } + + public static void system(String[] command, File directory) throws IOException { + system(asList(command), directory); + } + + public static void system(List command, File directory) throws IOException { + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.inheritIO(); + + if (directory != null) { + processBuilder.directory(directory); + } + + // DEBUG + debug.finest(format("Execute %s", command)); + + try { + int exitCode = processBuilder.start().waitFor(); + if (exitCode != 0) { + throw new ExecuteException(command, exitCode); + } + } catch (InterruptedException e) { + throw new IOException(String.format("%s timed out", command), e); + } + } + +} diff --git a/source/net/filebot/ExecuteException.java b/source/net/filebot/ExecuteException.java new file mode 100644 index 00000000..2abb4af6 --- /dev/null +++ b/source/net/filebot/ExecuteException.java @@ -0,0 +1,23 @@ +package net.filebot; + +import java.io.IOException; +import java.util.List; + +public class ExecuteException extends IOException { + + private int exitCode; + + public ExecuteException(String message, int exitCode) { + super(message); + this.exitCode = exitCode; + } + + public ExecuteException(List command, int exitCode) { + this(String.format("%s failed with exit code %d", command, exitCode), exitCode); + } + + public int getExitCode() { + return exitCode; + } + +} diff --git a/source/net/filebot/archive/ShellExecutables.java b/source/net/filebot/archive/ShellExecutables.java index 605888f6..b1dd21bd 100644 --- a/source/net/filebot/archive/ShellExecutables.java +++ b/source/net/filebot/archive/ShellExecutables.java @@ -1,20 +1,16 @@ package net.filebot.archive; -import static java.nio.charset.StandardCharsets.*; -import static java.util.Arrays.*; import static java.util.stream.Collectors.*; -import static net.filebot.Logging.*; +import static net.filebot.Execute.*; import static net.filebot.util.RegularExpressions.*; import java.io.File; import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; -import java.lang.ProcessBuilder.Redirect; import java.util.List; import java.util.regex.Pattern; -import net.filebot.util.ByteBufferOutputStream; import net.filebot.util.FileUtilities.ExtensionFileFilter; import net.filebot.vfs.FileInfo; import net.filebot.vfs.SimpleFileInfo; @@ -48,37 +44,10 @@ public class ShellExecutables implements ArchiveExtractor { command.extract(archive, outputFolder.getCanonicalFile(), filter); } - protected static Command getCommand(File archive) { + protected Command getCommand(File archive) { return RAR_FILES.accept(archive) ? Command.unrar : Command.p7zip; } - protected static CharSequence execute(String[]... command) throws IOException { - return execute(stream(command).flatMap(a -> stream(a)).toArray(String[]::new)); - } - - protected static CharSequence execute(String... command) throws IOException { - Process process = new ProcessBuilder(command).redirectError(Redirect.INHERIT).start(); - - try (ByteBufferOutputStream bb = new ByteBufferOutputStream(8 * 1024)) { - bb.transferFully(process.getInputStream()); - - int returnCode = process.waitFor(); - String output = UTF_8.decode(bb.getByteBuffer()).toString(); - - // DEBUG - debug.fine(format("Execute: %s", asList(command))); - debug.finest(output); - - if (returnCode == 0) { - return output; - } else { - throw new IOException(String.format("%s failed with exit code %d: %s", asList(command), returnCode, output.trim())); - } - } catch (InterruptedException e) { - throw new IOException(String.format("%s timed out", asList(command)), e); - } - } - public enum Command { p7zip { diff --git a/source/net/filebot/cli/ExecCommand.java b/source/net/filebot/cli/ExecCommand.java index 53f6016c..9e5a136f 100644 --- a/source/net/filebot/cli/ExecCommand.java +++ b/source/net/filebot/cli/ExecCommand.java @@ -1,6 +1,7 @@ package net.filebot.cli; import static java.util.stream.Collectors.*; +import static net.filebot.Execute.*; import static net.filebot.Logging.*; import java.io.File; @@ -28,7 +29,7 @@ public class ExecCommand { this.directory = directory; } - public void execute(MediaBindingBean... group) throws IOException, InterruptedException { + public void execute(MediaBindingBean... group) throws IOException { if (parallel) { executeParallel(group); } else { @@ -36,7 +37,7 @@ public class ExecCommand { } } - private void executeSequence(MediaBindingBean... group) throws IOException, InterruptedException { + private void executeSequence(MediaBindingBean... group) throws IOException { // collect unique commands List> commands = Stream.of(group).map(v -> { return template.stream().map(t -> getArgumentValue(t, v)).filter(Objects::nonNull).collect(toList()); @@ -44,18 +45,18 @@ public class ExecCommand { // execute unique commands for (List command : commands) { - execute(command); + system(command, directory); } } - private void executeParallel(MediaBindingBean... group) throws IOException, InterruptedException { + private void executeParallel(MediaBindingBean... group) throws IOException { // collect single command List command = template.stream().flatMap(t -> { return Stream.of(group).map(v -> getArgumentValue(t, v)).filter(Objects::nonNull).distinct(); }).collect(toList()); // execute single command - execute(command); + system(command, directory); } private String getArgumentValue(ExpressionFormat template, MediaBindingBean variables) { @@ -67,19 +68,6 @@ public class ExecCommand { return null; } - private void execute(List command) throws IOException, InterruptedException { - ProcessBuilder process = new ProcessBuilder(command); - process.directory(directory); - process.inheritIO(); - - debug.finest(format("Execute %s", command)); - - int exitCode = process.start().waitFor(); - if (exitCode != 0) { - throw new IOException(String.format("%s failed with exit code %d", command, exitCode)); - } - } - public static ExecCommand parse(List args, File directory) throws ScriptException { // execute one command per file or one command with many file arguments boolean parallel = args.lastIndexOf("+") == args.size() - 1; diff --git a/source/net/filebot/cli/ExecutableRenameAction.java b/source/net/filebot/cli/ExecutableRenameAction.java index f86f10c3..706a86f8 100644 --- a/source/net/filebot/cli/ExecutableRenameAction.java +++ b/source/net/filebot/cli/ExecutableRenameAction.java @@ -1,7 +1,8 @@ package net.filebot.cli; +import static net.filebot.Execute.*; + import java.io.File; -import java.io.IOException; import net.filebot.RenameAction; @@ -17,14 +18,9 @@ public class ExecutableRenameAction implements RenameAction { @Override public File rename(File from, File to) throws Exception { - ProcessBuilder process = new ProcessBuilder(executable, from.getCanonicalPath(), getRelativePath(directory, to)); - process.directory(directory); - process.inheritIO(); + String[] command = { executable, from.getCanonicalPath(), getRelativePath(directory, to) }; - int exitCode = process.start().waitFor(); - if (exitCode != 0) { - throw new IOException(String.format("%s failed with exit code %d", process.command(), exitCode)); - } + system(command, directory); return null; } diff --git a/source/net/filebot/media/FFProbe.java b/source/net/filebot/media/FFProbe.java index 1bef9e06..7cb53ead 100644 --- a/source/net/filebot/media/FFProbe.java +++ b/source/net/filebot/media/FFProbe.java @@ -3,10 +3,10 @@ package net.filebot.media; import static java.util.Arrays.*; import static java.util.Collections.*; import static java.util.stream.Collectors.*; +import static net.filebot.Execute.*; import java.io.File; import java.io.IOException; -import java.lang.ProcessBuilder.Redirect; import java.time.Duration; import java.time.Instant; import java.util.List; @@ -25,23 +25,12 @@ public class FFProbe implements MediaCharacteristics { } protected Map parse(File file) throws IOException, InterruptedException { - ProcessBuilder processBuilder = new ProcessBuilder(getFFProbeCommand(), "-show_streams", "-show_format", "-print_format", "json", "-v", "error", file.getCanonicalPath()); + String[] command = { getFFProbeCommand(), "-show_streams", "-show_format", "-print_format", "json", "-v", "error", file.getCanonicalPath() }; - processBuilder.directory(file.getParentFile()); - processBuilder.redirectError(Redirect.INHERIT); - - Process process = processBuilder.start(); + CharSequence output = execute(command, file.getParentFile()); // parse process standard output - Map json = (Map) JsonReader.jsonToJava(process.getInputStream(), singletonMap(JsonReader.USE_MAPS, true)); - - int exitCode = process.waitFor(); - if (exitCode != 0) { - throw new IOException(String.format("%s failed with exit code %d", processBuilder.command(), exitCode)); - } - - // group video / audio / subtitle streams together - return json; + return (Map) JsonReader.jsonToJava(output.toString(), singletonMap(JsonReader.USE_MAPS, true)); } private Map json;