/*
* Copyright (C) 2016 Patrick Favre-Bulle
*
* 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 at.favre.tools.dconvert;
import at.favre.tools.dconvert.arg.Arguments;
import at.favre.tools.dconvert.arg.EPlatform;
import at.favre.tools.dconvert.converters.IPlatformConverter;
import at.favre.tools.dconvert.converters.postprocessing.IPostProcessor;
import at.favre.tools.dconvert.converters.postprocessing.MozJpegProcessor;
import at.favre.tools.dconvert.converters.postprocessing.PngCrushProcessor;
import at.favre.tools.dconvert.converters.postprocessing.WebpProcessor;
import at.favre.tools.dconvert.converters.scaling.ImageHandler;
import at.favre.tools.dconvert.converters.scaling.ScaleAlgorithm;
import at.favre.tools.dconvert.util.MiscUtil;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import java.io.File;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* This is the main class handling all of the converters and post processors.
* This handles the threading and orchestration of the threads.
* <p>
* All user interfaces will call this class to execute.
*/
public class DConvert {
private CountDownLatch mainLatch;
private HandlerCallback handlerCallback;
private long beginMs;
private StringBuilder logStringBuilder = new StringBuilder();
/**
* Starts the execution of the dconvert
*
* @param args from user interface
* @param blockingWaitForFinish if true will block the thread until all threads are finished
* @param callback main callback
*/
public void execute(Arguments args, boolean blockingWaitForFinish, HandlerCallback callback) {
beginMs = System.currentTimeMillis();
handlerCallback = callback;
logStringBuilder.append("registered image readers:\n").append(getRegisteredImageReadersAndWriters()).append("\n");
logStringBuilder.append("begin execution using ").append(args.threadCount).append(" theads\n");
logStringBuilder.append("args: ").append(args).append("\n");
if (!args.filesToProcess.isEmpty()) {
List<IPlatformConverter> converters = new ArrayList<>();
List<IPostProcessor> postProcessors = new ArrayList<>();
for (EPlatform ePlatform : args.platform) {
logStringBuilder.append("add ").append(ePlatform.getConverter().getClass().getSimpleName()).append("\n");
converters.add(ePlatform.getConverter());
}
if (args.clearDirBeforeConvert) {
logStringBuilder.append("clear out dirs before convert\n");
for (IPlatformConverter converter : converters) {
converter.clean(args);
}
}
if (args.enablePngCrush) {
IPostProcessor postProcessor = new PngCrushProcessor();
if (postProcessor.isSupported()) {
logStringBuilder.append("add pngcrush postprocessor\n");
postProcessors.add(postProcessor);
} else {
logStringBuilder.append("WARNING: Tool 'pngcrush' cannot be accessed. Is it set in PATH?\n");
}
}
if (args.postConvertWebp) {
IPostProcessor postProcessor = new WebpProcessor();
if (postProcessor.isSupported()) {
logStringBuilder.append("add cwebp postprocessor\n");
postProcessors.add(postProcessor);
} else {
logStringBuilder.append("WARNING: Tool 'cwebp' cannot be accessed. Is it set in PATH?\n");
}
}
if (args.enableMozJpeg) {
IPostProcessor postProcessor = new MozJpegProcessor();
if (postProcessor.isSupported()) {
logStringBuilder.append("add mozJpeg postprocessor\n");
postProcessors.add(postProcessor);
} else {
logStringBuilder.append("WARNING: Tool 'jpegtran' cannot be accessed. Is it set in PATH?\n");
}
}
int convertJobs = args.filesToProcess.size() * converters.size();
int postProcessorJobs = convertJobs * postProcessors.size();
float convertPercentage = (float) convertJobs / (float) (convertJobs + postProcessorJobs);
float postProcessPercentage = (float) postProcessorJobs / (float) (convertJobs + postProcessorJobs);
mainLatch = new CountDownLatch(1);
for (File srcFile : args.filesToProcess) {
logStringBuilder.append("add ").append(srcFile).append(" to processing queue\n");
if (!srcFile.exists() || !srcFile.isFile()) {
throw new IllegalStateException("srcFile " + srcFile + " does not exist");
}
}
new WorkerHandler<>(converters, args, new WorkerHandler.Callback() {
@Override
public void onProgress(float percent) {
handlerCallback.onProgress(convertPercentage * percent);
}
@Override
public void onFinished(final int finishedJobsConverters, List<File> outFiles, final StringBuilder logConverters, final List<Exception> exceptionsConverters, final boolean haltedDuringProcessConverters) {
logStringBuilder.append(logConverters);
if (haltedDuringProcessConverters) {
informFinished(finishedJobsConverters, exceptionsConverters, true);
} else {
new WorkerHandler<>(postProcessors, args, new WorkerHandler.Callback() {
@Override
public void onProgress(float percent) {
handlerCallback.onProgress(convertPercentage + (postProcessPercentage * percent));
}
@Override
public void onFinished(int finishedJobsPostProcessors, List<File> outFiles, StringBuilder log, List<Exception> exceptions, boolean haltedDuringProcess) {
exceptionsConverters.addAll(exceptions);
logStringBuilder.append(log);
informFinished(finishedJobsPostProcessors + finishedJobsConverters, exceptionsConverters, haltedDuringProcess);
}
}).start(outFiles);
}
}
}).start(args.filesToProcess);
if (blockingWaitForFinish) {
try {
mainLatch.await(60, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
logStringBuilder.append("no files to convert\n");
informFinished(0, Collections.emptyList(), false);
}
}
private void informFinished(int finishedJobs, List<Exception> exceptions, boolean haltedDuringProcess) {
System.gc();
printTrace();
if (handlerCallback != null) {
if (mainLatch != null) {
mainLatch.countDown();
}
for (Exception exception : exceptions) {
logStringBuilder.append(MiscUtil.getStackTrace(exception)).append("\n");
}
handlerCallback.onFinished(finishedJobs, exceptions, (System.currentTimeMillis() - beginMs), haltedDuringProcess, logStringBuilder.toString().trim());
}
}
public interface HandlerCallback {
void onProgress(float progress);
void onFinished(int finishedJobs, List<Exception> exceptions, long time, boolean haltedDuringProcess, String log);
}
private String getRegisteredImageReadersAndWriters() {
String[] formats = new String[]{"JPEG", "PNG", "TIFF", "PSD", "SVG", "BMP"};
StringBuilder sb = new StringBuilder();
for (String format : Arrays.asList(formats)) {
Iterator<ImageReader> reader = ImageIO.getImageReadersByFormatName(format);
while (reader.hasNext()) {
ImageReader next = reader.next();
sb.append("reader: ").append(next).append("\n");
}
Iterator<ImageWriter> writer = ImageIO.getImageWritersByFormatName(format);
while (writer.hasNext()) {
ImageWriter next = writer.next();
sb.append("writer: ").append(next).append("\n");
}
}
return sb.toString();
}
private void printTrace() {
if (ImageHandler.TEST_MODE) {
for (Map.Entry<ScaleAlgorithm, Long> entry : ImageHandler.traceMap.entrySet()) {
System.out.println(entry.getKey() + ": " + String.format(Locale.US, "%.2f", (double) entry.getValue() / 1000000.0));
}
}
}
}