From 76ea1935c16f910d91671cc4b082b7607df190a0 Mon Sep 17 00:00:00 2001 From: Oondanomala <87101222+Oondanomala@users.noreply.github.com> Date: Sat, 30 Aug 2025 19:19:11 +0200 Subject: [PATCH] Allow specifying a config file --- .../exiftool/ExecutionStrategy.java | 15 +++++++ .../exiftool/ExifToolBuilder.java | 39 ++++++++++++++++++ .../core/strategies/DefaultStrategy.java | 17 +++++++- .../core/strategies/PoolStrategy.java | 7 ++++ .../core/strategies/StayOpenStrategy.java | 17 +++++++- .../exiftool/ExifToolBuilderTest.java | 19 +++++++++ .../core/strategies/DefaultStrategyTest.java | 40 +++++++++++++++++++ .../core/strategies/PoolStrategyTest.java | 27 +++++++++++++ .../core/strategies/StayOpenStrategyTest.java | 35 ++++++++++++++++ .../it/builder/AbstractExifToolIT.java | 14 +++++++ .../it/builder/ExifToolCustomExecutorIT.java | 5 +++ .../exiftool/tests/TestConstants.java | 6 +++ src/test/resources/exiftool.config | 1 + 13 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/exiftool.config diff --git a/src/main/java/com/thebuzzmedia/exiftool/ExecutionStrategy.java b/src/main/java/com/thebuzzmedia/exiftool/ExecutionStrategy.java index 039b79ee..bbdc0153 100644 --- a/src/main/java/com/thebuzzmedia/exiftool/ExecutionStrategy.java +++ b/src/main/java/com/thebuzzmedia/exiftool/ExecutionStrategy.java @@ -60,6 +60,21 @@ public interface ExecutionStrategy extends AutoCloseable { */ void execute(CommandExecutor executor, String exifTool, List arguments, OutputHandler handler) throws IOException; + /** + * Set the ExifTool config file path. + * The default is {@code .ExifTool_config}. + *

+ * Use {@code null} to specify no custom config path, + * and an empty string to disable loading the default config file. + * + * @param configPath Path to the new config path, or {@code null} for no config path. + * @implNote The default implementation does nothing, and only exists for backwards compatibility. + * Implementers should always override it. + * @see exiftool.org/config.html + */ + default void setConfigFilePath(String configPath) { + } + /** * Check if exiftool process is currently running. * This method is important especially if {@code stay_open} flag has been enabled. diff --git a/src/main/java/com/thebuzzmedia/exiftool/ExifToolBuilder.java b/src/main/java/com/thebuzzmedia/exiftool/ExifToolBuilder.java index 5bf921a1..c3151b3a 100644 --- a/src/main/java/com/thebuzzmedia/exiftool/ExifToolBuilder.java +++ b/src/main/java/com/thebuzzmedia/exiftool/ExifToolBuilder.java @@ -153,6 +153,11 @@ public class ExifToolBuilder { */ private String path; + /** + * ExifTool config file path. + */ + private String configPath; + /** * ExifTool executor. */ @@ -229,6 +234,39 @@ public ExifToolBuilder withPath(File path) { return this; } + /** + * Override the default ExifTool config path. + * The default path is {@code .ExifTool_config}. + *

+ * Set this to an empty string to disable loading the default config file. + * + * @param configPath New config path. + * @return Current builder. + * @see exiftool.org/config.html + */ + public ExifToolBuilder withConfig(String configPath) { + log.debug("Set configPath: {}", configPath); + this.configPath = configPath; + return this; + } + + /** + * Override the default ExifTool config path. + * The default path is {@code .ExifTool_config}. + *

+ * Call {@link #withConfig(String)} with an empty string to + * disable loading the default config file. + * + * @param configPath New config path. + * @return Current builder. + * @see exiftool.org/config.html + */ + public ExifToolBuilder withConfig(File configPath) { + log.debug("Set configPath: {}", configPath); + this.configPath = configPath.getAbsolutePath(); + return this; + } + /** * Override default exifTool executor. * @@ -405,6 +443,7 @@ public ExifTool build() { String path = firstNonNull(this.path, PATH); CommandExecutor executor = firstNonNull(this.executor, EXECUTOR); ExecutionStrategy strategy = firstNonNull(this.strategy, new StrategyFunction(stayOpen, cleanupDelay, scheduler, poolSize)); + strategy.setConfigFilePath(configPath); // Add some debugging information if (log.isDebugEnabled()) { diff --git a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategy.java b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategy.java index 3e8f64c5..763ac652 100644 --- a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategy.java +++ b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategy.java @@ -49,6 +49,12 @@ public class DefaultStrategy implements ExecutionStrategy { */ private static final Logger log = LoggerFactory.getLogger(DefaultStrategy.class); + /** + * Path to the ExifTool config file, + * or {@code null} if no custom config file is specified. + */ + private String configPath; + /** * Create strategy. */ @@ -59,7 +65,11 @@ public DefaultStrategy() { public void execute(CommandExecutor executor, String exifTool, List arguments, OutputHandler handler) throws IOException { log.debug("Using ExifTool in non-daemon mode (-stay_open False)..."); - Command cmd = CommandBuilder.builder(exifTool, arguments.size() + 2) + CommandBuilder cmdBuilder = CommandBuilder.builder(exifTool, arguments.size() + (configPath == null ? 2 : 4)); + if (configPath != null) { + cmdBuilder.addArgument("-config", configPath); + } + Command cmd = cmdBuilder .addArgument("-sep", Constants.SEPARATOR) .addAll(arguments) .build(); @@ -67,6 +77,11 @@ public void execute(CommandExecutor executor, String exifTool, List argu executor.execute(cmd, handler); } + @Override + public void setConfigFilePath(String configPath) { + this.configPath = configPath; + } + @Override public boolean isSupported(Version version) { // Always true. diff --git a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategy.java b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategy.java index e9ef6900..290ef28b 100644 --- a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategy.java +++ b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategy.java @@ -94,6 +94,13 @@ public void execute(CommandExecutor executor, String exifTool, List argu } } + @Override + public void setConfigFilePath(String configPath) { + for (ExecutionStrategy executionStrategy : pool) { + executionStrategy.setConfigFilePath(configPath); + } + } + @Override public boolean isRunning() { return pool.size() < poolSize; diff --git a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategy.java b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategy.java index 83e6f1c1..b1604fc7 100644 --- a/src/main/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategy.java +++ b/src/main/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategy.java @@ -60,6 +60,12 @@ public class StayOpenStrategy implements ExecutionStrategy { */ private CommandProcess process; + /** + * Path to the ExifTool config file, + * or {@code null} if no custom config file is specified. + */ + private String configPath; + /** * Create strategy. * Scheduler provided in parameter will be used to clean resources (exiftool process). @@ -82,7 +88,11 @@ public void execute(CommandExecutor executor, String exifTool, List argu // ready to receive commands from us. if (process == null || process.isClosed()) { log.debug("Start exiftool process"); - process = executor.start(CommandBuilder.builder(exifTool, 6) + CommandBuilder cmdBuilder = CommandBuilder.builder(exifTool, (configPath == null ? 6 : 8)); + if (configPath != null) { + cmdBuilder.addArgument("-config", configPath); + } + process = executor.start(cmdBuilder .addArgument("-stay_open", "True") .addArgument("-sep", Constants.SEPARATOR) .addArgument("-@") @@ -106,6 +116,11 @@ public void execute(CommandExecutor executor, String exifTool, List argu } } + @Override + public void setConfigFilePath(String configPath) { + this.configPath = configPath; + } + @Override public synchronized boolean isRunning() { return process != null && process.isRunning(); diff --git a/src/test/java/com/thebuzzmedia/exiftool/ExifToolBuilderTest.java b/src/test/java/com/thebuzzmedia/exiftool/ExifToolBuilderTest.java index 12b7cb55..68c19b3c 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/ExifToolBuilderTest.java +++ b/src/test/java/com/thebuzzmedia/exiftool/ExifToolBuilderTest.java @@ -57,6 +57,7 @@ public class ExifToolBuilderTest { private ExecutionStrategy strategy; private Scheduler scheduler; private String path; + private String configPath; private ExifToolBuilder builder; @@ -68,6 +69,7 @@ void setUp() throws Exception { builder = new ExifToolBuilder(); path = "/foo"; + configPath = "/bar"; // Mock ExifTool Version CommandResult v9_36 = new CommandResultBuilder() @@ -93,6 +95,21 @@ void it_should_update_path_with_file() { assertThat(builder).extracting("path").isEqualTo(file.getAbsolutePath()); } + @Test + void it_should_update_config_path() { + ExifToolBuilder res = builder.withConfig(configPath); + assertThat(res).isSameAs(builder); + assertThat(builder).extracting("configPath").isEqualTo(configPath); + } + + @Test + void it_should_update_config_path_with_file() { + File file = new FileBuilder("config.config").build(); + ExifToolBuilder res = builder.withConfig(file); + assertThat(res).isSameAs(builder); + assertThat(builder).extracting("configPath").isEqualTo(file.getAbsolutePath()); + } + @Test void it_should_update_executor() { ExifToolBuilder res = builder.withExecutor(executor); @@ -149,6 +166,7 @@ void it_should_override_strategy() { void it_should_create_exiftool_with_custom_props() { ExifTool exifTool = builder .withPath(path) + .withConfig(configPath) .withExecutor(executor) .enableStayOpen() .build(); @@ -156,6 +174,7 @@ void it_should_create_exiftool_with_custom_props() { assertThat(exifTool).extracting("path").isEqualTo(path); assertThat(exifTool).extracting("executor").isEqualTo(executor); assertThat(exifTool).extracting("strategy").isExactlyInstanceOf(StayOpenStrategy.class); + assertThat(exifTool).extracting("strategy").extracting("configPath").isEqualTo(configPath); } @Test diff --git a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategyTest.java b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategyTest.java index be0b835a..a451fa14 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategyTest.java +++ b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/DefaultStrategyTest.java @@ -34,6 +34,7 @@ @SuppressWarnings("resource") class DefaultStrategyTest { + private final String configPath = "config.config"; @Test void it_should_execute_command() throws Exception { @@ -58,6 +59,45 @@ void it_should_execute_command() throws Exception { assertThat(cmd.getArguments()).hasSameSizeAs(expectedArguments).isEqualTo(expectedArguments); } + @Test + void it_should_execute_command_with_config_path() throws Exception { + String exifTool = "exiftool"; + List args = asList("-S", "-n", "-XArtist", "-XComment", "-execute"); + CommandExecutor executor = mock(CommandExecutor.class); + OutputHandler handler = mock(OutputHandler.class); + + DefaultStrategy strategy = new DefaultStrategy(); + strategy.setConfigFilePath(configPath); + strategy.execute(executor, exifTool, args, handler); + + ArgumentCaptor cmdCaptor = ArgumentCaptor.forClass(Command.class); + verify(executor).execute(cmdCaptor.capture(), same(handler)); + + List expectedArguments = new ArrayList<>(); + expectedArguments.add(exifTool); + expectedArguments.add("-config"); + expectedArguments.add(configPath); + expectedArguments.add("-sep"); + expectedArguments.add("|>☃"); + expectedArguments.addAll(args); + + Command cmd = cmdCaptor.getValue(); + assertThat(cmd.getArguments()).hasSameSizeAs(expectedArguments).isEqualTo(expectedArguments); + } + + + @Test + void it_should_not_have_config_path_if_none_is_set() { + assertThat(new DefaultStrategy()).extracting("configPath").isNull(); + } + + @Test + void it_should_set_config_path() { + DefaultStrategy strategy = new DefaultStrategy(); + strategy.setConfigFilePath(configPath); + assertThat(strategy).extracting("configPath").isEqualTo(configPath); + } + @Test void it_should_never_be_running() { DefaultStrategy strategy = new DefaultStrategy(); diff --git a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategyTest.java b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategyTest.java index 25a4f073..f9cf357b 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategyTest.java +++ b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/PoolStrategyTest.java @@ -51,6 +51,7 @@ class PoolStrategyTest { private CommandExecutor executor; private String exifTool; + private String configPath; private List arguments; private OutputHandler handler; @@ -60,6 +61,7 @@ class PoolStrategyTest { void setUp() { executor = mock(CommandExecutor.class); exifTool = "exiftool"; + configPath = "config.config"; arguments = singletonList("-ver"); handler = mock(OutputHandler.class); } @@ -129,6 +131,31 @@ void it_should_check_that_version_is_supported() { assertThat(pool.isSupported(version)).isTrue(); } + @Test + void it_should_not_set_config_path_if_none_is_set() { + ExecutionStrategy s1 = mock(ExecutionStrategy.class); + ExecutionStrategy s2 = mock(ExecutionStrategy.class); + Collection strategies = asList(s1, s2); + + pool = new PoolStrategy(strategies); + + verify(s1, never()).setConfigFilePath(configPath); + verify(s2, never()).setConfigFilePath(configPath); + } + + @Test + void it_should_set_config_path() { + ExecutionStrategy s1 = mock(ExecutionStrategy.class); + ExecutionStrategy s2 = mock(ExecutionStrategy.class); + Collection strategies = asList(s1, s2); + + pool = new PoolStrategy(strategies); + pool.setConfigFilePath(configPath); + + verify(s1).setConfigFilePath(configPath); + verify(s2).setConfigFilePath(configPath); + } + @Test void it_should_execute_strategies_in_parallel() throws Exception { CountDownLatch executionLock = new CountDownLatch(1); diff --git a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategyTest.java b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategyTest.java index 16c987d0..d86176b2 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategyTest.java +++ b/src/test/java/com/thebuzzmedia/exiftool/core/strategies/StayOpenStrategyTest.java @@ -52,6 +52,7 @@ class StayOpenStrategyTest { private OutputHandler outputHandler; private String exifTool; + private String configPath; private List args; private StayOpenStrategy strategy; @@ -62,6 +63,7 @@ void setUp() throws Exception { executor = mock(CommandExecutor.class); outputHandler = mock(OutputHandler.class); exifTool = "exiftool"; + configPath = "config.config"; // Mock withExecutor when(executor.start(any(Command.class))).thenReturn(process); @@ -87,6 +89,14 @@ void it_should_create_stay_open_strategy() { strategy = new StayOpenStrategy(scheduler); assertThat(strategy).extracting("scheduler").isSameAs(scheduler); assertThat(strategy).extracting("process").isNull(); + assertThat(strategy).extracting("configPath").isNull(); + } + + @Test + void it_should_set_config_path() { + StayOpenStrategy strategy = new StayOpenStrategy(scheduler); + strategy.setConfigFilePath(configPath); + assertThat(strategy).extracting("configPath").isEqualTo(configPath); } @SuppressWarnings("unchecked") @@ -111,6 +121,31 @@ void it_should_execute_command() throws Exception { verifyExecutionArguments(argsCaptor); } + @Test + void it_should_execute_command_with_config_path() throws Exception { + strategy = new StayOpenStrategy(scheduler); + strategy.setConfigFilePath(configPath); + strategy.execute(executor, exifTool, args, outputHandler); + + ArgumentCaptor cmdCaptor = ArgumentCaptor.forClass(Command.class); + ArgumentCaptor> argsCaptor = ArgumentCaptor.forClass(List.class); + InOrder inOrder = inOrder(scheduler, executor, process); + inOrder.verify(executor).start(cmdCaptor.capture()); + inOrder.verify(scheduler).stop(); + inOrder.verify(scheduler).start(any(Runnable.class)); + inOrder.verify(process).write(argsCaptor.capture()); + inOrder.verify(process).flush(); + inOrder.verify(process).read(any(OutputHandler.class)); + + assertThat(strategy).extracting("process").isSameAs(process); + + Command startCmd = cmdCaptor.getValue(); + assertThat(startCmd.getArguments()).hasSize(9).containsExactly( + exifTool, "-config", configPath, "-stay_open", "True", "-sep", "|>☃", "-@", "-" + ); + verifyExecutionArguments(argsCaptor); + } + @SuppressWarnings("unchecked") @Test void it_should_not_start_process_twice_if_it_is_running() throws Exception { diff --git a/src/test/java/com/thebuzzmedia/exiftool/it/builder/AbstractExifToolIT.java b/src/test/java/com/thebuzzmedia/exiftool/it/builder/AbstractExifToolIT.java index 5c20685f..d390d0a5 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/it/builder/AbstractExifToolIT.java +++ b/src/test/java/com/thebuzzmedia/exiftool/it/builder/AbstractExifToolIT.java @@ -24,9 +24,11 @@ import org.junit.jupiter.api.Test; import java.nio.file.FileSystems; +import java.util.Arrays; import java.util.UUID; import static com.thebuzzmedia.exiftool.tests.TestConstants.EXIF_TOOL; +import static com.thebuzzmedia.exiftool.tests.TestConstants.EXIF_TOOL_CONFIG; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -54,5 +56,17 @@ void it_should_fail_with_missing_exiftool() { .hasMessageContaining("error=2"); } + @Test + void it_should_read_config() throws Exception { + ExifToolBuilder builder = create() + .withPath(EXIF_TOOL.getAbsolutePath()) + .withConfig(EXIF_TOOL_CONFIG.getAbsolutePath()); + + try (ExifTool exifTool = builder.build()) { + String result = exifTool.getRawExifToolOutput(Arrays.asList("-ver", "-execute")); + assertThat(result).startsWith(EXIF_TOOL_CONFIG.getAbsolutePath() + " did not return a true value at "); + } + } + abstract ExifToolBuilder create(); } diff --git a/src/test/java/com/thebuzzmedia/exiftool/it/builder/ExifToolCustomExecutorIT.java b/src/test/java/com/thebuzzmedia/exiftool/it/builder/ExifToolCustomExecutorIT.java index e2e43d18..aeb2f1a2 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/it/builder/ExifToolCustomExecutorIT.java +++ b/src/test/java/com/thebuzzmedia/exiftool/it/builder/ExifToolCustomExecutorIT.java @@ -87,4 +87,9 @@ public String getOutput() { return sb.toString().trim(); } } + + @Override + void it_should_read_config() { + // Cannot run as execute(Command, OutputHandler) is not implemented. + } } diff --git a/src/test/java/com/thebuzzmedia/exiftool/tests/TestConstants.java b/src/test/java/com/thebuzzmedia/exiftool/tests/TestConstants.java index e047cf92..62111da7 100644 --- a/src/test/java/com/thebuzzmedia/exiftool/tests/TestConstants.java +++ b/src/test/java/com/thebuzzmedia/exiftool/tests/TestConstants.java @@ -32,6 +32,8 @@ private TestConstants() { public static final File EXIF_TOOL; + public static final File EXIF_TOOL_CONFIG; + static { final String osName = System.getProperty("os.name").toLowerCase(); final boolean isWindows = osName.contains("windows"); @@ -41,7 +43,11 @@ private TestConstants() { file.setExecutable(true); file.setReadable(true); + final File configFile = new File(AbstractExifToolImgIT.class.getResource("/exiftool.config").getFile()); + file.setReadable(true); + IS_WINDOWS = isWindows; EXIF_TOOL = file; + EXIF_TOOL_CONFIG = configFile; } } diff --git a/src/test/resources/exiftool.config b/src/test/resources/exiftool.config new file mode 100644 index 00000000..0f57817f --- /dev/null +++ b/src/test/resources/exiftool.config @@ -0,0 +1 @@ +0;