package net.thucydides.core.screenshots;
import com.google.inject.Inject;
import net.thucydides.core.ThucydidesSystemProperty;
import net.thucydides.core.util.EnvironmentVariables;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
public class SingleThreadScreenshotProcessor implements ScreenshotProcessor {
Thread screenshotThread;
final Queue<QueuedScreenshot> queue;
private final EnvironmentVariables environmentVariables;
private final Logger logger = LoggerFactory.getLogger(SingleThreadScreenshotProcessor.class);
@Inject
public SingleThreadScreenshotProcessor(EnvironmentVariables environmentVariables) {
this.environmentVariables = environmentVariables;
this.queue = new ConcurrentLinkedQueue<>();
start();
}
public void start() {
screenshotThread = new Thread(new Processor(queue));
screenshotThread.setDaemon(true);
screenshotThread.start();
}
public void waitUntilDone() {
while (!isEmpty()) {
try {
Thread.sleep(10);
} catch (InterruptedException ignore) {
}
}
}
boolean done = false;
public void terminate() {
done = true;
}
class Processor implements Runnable {
private final Queue<QueuedScreenshot> queue;
Processor(Queue<QueuedScreenshot> queue) {
this.queue = queue;
}
public void run() {
while (!done) {
synchronized (queue) {
saveQueuedScreenshot();
try {
if (!done) {
queue.wait();
}
} catch (InterruptedException ignore) {
}
}
}
}
private void saveQueuedScreenshot() {
while (!queue.isEmpty()) {
QueuedScreenshot queuedScreenshot = queue.poll();
if (queuedScreenshot != null) {
processScreenshot(queuedScreenshot);
}
}
}
private void processScreenshot(QueuedScreenshot queuedScreenshot) {
if (!queuedScreenshot.getDestinationFile().exists()) {
resizeOrMoveScreenshot(queuedScreenshot);
}
}
private void resizeOrMoveScreenshot(QueuedScreenshot queuedScreenshot) {
if (shouldResize(queuedScreenshot)) {
resizeScreenshot(queuedScreenshot);
} else {
moveScreenshot(queuedScreenshot);
}
}
private int getResizedWidth() {
return environmentVariables.getPropertyAsInteger(ThucydidesSystemProperty.THUCYDIDES_RESIZED_IMAGE_WIDTH, 0);
}
private boolean shouldResize(QueuedScreenshot queuedScreenshot) {
if (getResizedWidth() > 0) {
BufferedImage image = readImage(queuedScreenshot);
if (image != null) {
int width = image.getData().getWidth();
return (width != getResizedWidth());
}
}
return false;
}
private BufferedImage readImage(QueuedScreenshot queuedScreenshot) {
BufferedImage image = null;
try {
image = ImageIO.read(queuedScreenshot.getSourceFile());
} catch (IOException e) {
logger.warn("Failed to read the stored screenshot (possibly an out of memory error): " + e.getMessage());
}
return image;
}
private void moveScreenshot(QueuedScreenshot queuedScreenshot) {
try {
CopyOption[] options = new CopyOption[]{ StandardCopyOption.COPY_ATTRIBUTES };
Path sourcePath = queuedScreenshot.getSourceFile().toPath();
Path destinationPath = queuedScreenshot.getDestinationFile().toPath();
Path destinationDir = queuedScreenshot.getDestinationFile().toPath().getParent();
if (Files.notExists(destinationDir)) {
Files.createDirectories(destinationDir);
}
if (Files.notExists(destinationPath)) {
Files.copy(sourcePath, destinationPath, options);
}
try {
Files.deleteIfExists(sourcePath);
} catch (IOException e) {
queuedScreenshot.getSourceFile().deleteOnExit();
}
} catch (Throwable e) {
logger.warn("Failed to copy the screenshot to the destination directory: " + e.getMessage());
}
}
private void resizeScreenshot(QueuedScreenshot queuedScreenshot) {
try {
BufferedImage image = ImageIO.read(queuedScreenshot.getSourceFile());
int width = image.getData().getWidth();
int height = image.getData().getHeight();
int targetWidth = getResizedWidth();
int targetHeight = (int) (((double) targetWidth / (double) width) * (double) height);
BufferedImage resizedImage = resize(image, targetWidth, targetHeight);
ImageIO.write(resizedImage, "png", queuedScreenshot.getDestinationFile());
FileUtils.deleteQuietly(queuedScreenshot.getSourceFile());
} catch (Throwable e) {
logger.warn("Failed to resize screenshot: using original size " + e.getMessage());
moveScreenshot(queuedScreenshot);
}
}
private BufferedImage resize(BufferedImage image, int width, int height) {
int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
BufferedImage resizedImage = new BufferedImage(width, height, type);
Graphics2D g = resizedImage.createGraphics();
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(image, 0, 0, width, height, null);
g.dispose();
return resizedImage;
}
}
public void queueScreenshot(QueuedScreenshot queuedScreenshot) {
queue.offer(queuedScreenshot);
synchronized (queue) {
queue.notifyAll();
}
}
public synchronized boolean isEmpty() {
return queue.isEmpty();
}
}