All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thebuzzmedia.exiftool.core.strategies.StayOpenStrategy Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
/**
 * Copyright 2011 The Buzz Media, LLC
 * Copyright 2015-2019 Mickael Jeanroy
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.thebuzzmedia.exiftool.core.strategies;

import com.thebuzzmedia.exiftool.Constants;
import com.thebuzzmedia.exiftool.ExecutionStrategy;
import com.thebuzzmedia.exiftool.Scheduler;
import com.thebuzzmedia.exiftool.Version;
import com.thebuzzmedia.exiftool.commons.iterables.Mapper;
import com.thebuzzmedia.exiftool.logs.Logger;
import com.thebuzzmedia.exiftool.logs.LoggerFactory;
import com.thebuzzmedia.exiftool.process.CommandExecutor;
import com.thebuzzmedia.exiftool.process.CommandProcess;
import com.thebuzzmedia.exiftool.process.OutputHandler;
import com.thebuzzmedia.exiftool.process.command.CommandBuilder;

import java.io.IOException;
import java.util.List;

import static com.thebuzzmedia.exiftool.commons.iterables.Collections.map;

/**
 * Execution strategy that use {@code exiftool} with the {@code stay_open} feature.
 */
public class StayOpenStrategy implements ExecutionStrategy {

	/**
	 * Class Logger.
	 */
	private static final Logger log = LoggerFactory.getLogger(StayOpenStrategy.class);

	/**
	 * Mapper to use to append a line break to each
	 * arguments.
	 */
	private static final ArgumentMapper MAPPER = new ArgumentMapper();

	/**
	 * Minimum version of {@code exiftool} supporting {@code stay_open} feature.
	 */
	private static final Version V8_36 = new Version("8.36");

	/**
	 * Scheduler: will be used to perform automatic cleanup.
	 * If automatic cleanup is disabled (if delay is equal or less than zero),
	 * then it will be set to {@code null}.
	 */
	private final Scheduler scheduler;

	/**
	 * Process opened when the first execution is called.
	 * This process will remain open until a call to {@link #close} is made.
	 */
	private CommandProcess process;

	/**
	 * Create strategy.
	 * Scheduler provided in parameter will be used to clean resources (exiftool process).
	 *
	 * @param scheduler Delay between automatic cleanup.
	 */
	public StayOpenStrategy(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	@Override
	public void execute(CommandExecutor executor, String exifTool, List arguments, OutputHandler handler) throws IOException {
		log.debug("Using ExifTool in daemon mode (-stay_open True)...");
		List newArgs = map(arguments, MAPPER);

		synchronized (this) {
			// Start daemon process if it is not already started.
			// If this is our first time calling getImageMeta with a "stayOpen"
			// connection, set up the persistent process and run it so it is
			// ready to receive commands from us.
			if (process == null || process.isClosed()) {
				log.debug("Start exiftool process");
				process = executor.start(CommandBuilder.builder(exifTool, 6)
						.addArgument("-stay_open", "True")
						.addArgument("-sep", Constants.SEPARATOR)
						.addArgument("-@")
						.addArgument("-")
						.build());
			}

			// Always reset the cleanup task.
			scheduler.stop();
			scheduler.start(new Runnable() {
				@Override
				public void run() {
					safeClose();
				}
			});

			try {
				process.write(newArgs);
				process.flush();
				process.read(handler);
			}
			catch (IOException ex) {
				log.error(ex.getMessage(), ex);
				throw ex;
			}
		}
	}

	@Override
	public synchronized boolean isRunning() {
		return process != null && process.isRunning();
	}

	@Override
	public boolean isSupported(Version version) {
		return V8_36.compareTo(version) <= 0;
	}

	@Override
	public synchronized void close() throws Exception {
		if (process != null) {
			closeProcess();
		}

		closeScheduler();
	}

	@Override
	public synchronized void shutdown() throws Exception {
		close();
		shutdownScheduler();
	}

	// Implement finalizer.
	// This is just a small security: it should not prevent caller
	// to explicitly close the exiftool process (but add this finalizer
	// if someone forget).
	@Override
	protected void finalize() throws Throwable {
		// Just delegate to original finalizer implementation
		super.finalize();

		// Be sure process is closed
		shutdown();
	}

	/**
	 * Close pending cleanup task and stop scheduler.
	 * This scheduler may be re-used if necessary.
	 */
	private synchronized void closeScheduler() {
		// Try to stop cleanup task
		// Note: If task is not stopped, it may be executed later

		try {
			log.debug("Attempting to stop cleanup task");
			scheduler.stop();
			log.debug("Cleanup task successfully stopped");
		}
		catch (Exception ex) {
			// Should not fail everything.
			// Important to log warning at least.
			log.warn("Cleanup task failed to stop");
			log.warn(ex.getMessage(), ex);
		}
	}

	/**
	 * Close pending cleanup task and stop scheduler.
	 * This scheduler may be re-used if necessary.
	 */
	private synchronized void shutdownScheduler() {
		// Try to stop cleanup task
		// Note: If task is not stopped, it may be executed later

		try {
			log.debug("Attempting to shutdown cleanup task");
			scheduler.shutdown();
			log.debug("Cleanup task successfully shutdown");
		}
		catch (Exception ex) {
			// Should not fail everything.
			// Important to log warning at least.
			log.warn("Cleanup task failed to shutdown");
			log.warn(ex.getMessage(), ex);
		}
	}

	/**
	 * Close ExifTool process.
	 * Process may be re-used if necessary.
	 *
	 * @throws Exception If an error occurs during the close operation.
	 */
	private synchronized void closeProcess() throws Exception {
		try {
			// If ExifTool was used in stayOpen mode but getImageMeta was never
			// called then the streams were never initialized and there is nothing
			// to shut down or destroy, otherwise we need to close down all the
			// resources in use.
			log.debug("Attempting to close ExifTool daemon process, issuing '-stay_open\\nFalse\\n' command...");
			process.write("-stay_open\nFalse\n");
			process.flush();
			process.close();
			log.debug("ExifTool daemon process successfully closed");
		}
		catch (Exception ex) {
			// Log some warnings.
			log.warn("ExifTool daemon failed to stop");
			log.warn(ex.getMessage(), ex);

			// Re-throw the error, this will let the caller do what he wants with the exception.
			throw ex;
		}
		finally {
			// Dot not forget to set it to null.
			process = null;
		}
	}

	/**
	 * This is exactly the same operation as {@link #close} but catch
	 * all exceptions and log stacktrace.
	 * This method should be used internally to perform a close operation
	 * without catching or propagate exceptions.
	 */
	private synchronized void safeClose() {
		try {
			close();
		}
		catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}
	}

	/**
	 * Argument Mapper.
	 * This mapper should be used to concatenate a line break after
	 * each input.
	 */
	private static class ArgumentMapper implements Mapper {

		@Override
		public String map(String input) {
			return input + Constants.BR;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy