/*
* Autopsy Forensic Browser
*
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* 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 org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.util.Objects;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Abstract base class for views of a single drawable file.
*/
@NbBundle.Messages({"DrawableUIBase.errorLabel.text=Could not read file",
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
static final Executor exec = Executors.newSingleThreadExecutor();
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
@FXML
BorderPane imageBorder;
@FXML
ImageView imageView;
private final ImageGalleryController controller;
private Optional<DrawableFile> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty();
private volatile Task<Image> imageTask;
public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller;
}
@Override
public ImageGalleryController getController() {
return controller;
}
@Override
public Optional<Long> getFileID() {
return fileIDOpt;
}
synchronized void setFileIDOpt(Optional<Long> fileIDOpt) {
this.fileIDOpt = fileIDOpt;
}
synchronized void setFileOpt(Optional<DrawableFile> fileOpt) {
this.fileOpt = fileOpt;
}
@Override
synchronized public Optional<DrawableFile> getFile() {
if (fileIDOpt.isPresent()) {
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
return fileOpt;
} else {
try {
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
} catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex); //NON-NLS
fileOpt = Optional.empty();
}
return fileOpt;
}
} else {
return Optional.empty();
}
}
protected abstract void setFileHelper(Long newFileID);
@Override
synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) {
setFileHelper(newFileID);
}
} else {
setFileHelper(newFileID);
}
}
synchronized protected void updateContent() {
if (getFile().isPresent()) {
doReadImageTask(getFile().get());
}
}
synchronized Node doReadImageTask(DrawableFile file) {
Task<Image> myTask = newReadImageTask(file);
imageTask = myTask;
Node progressNode = newProgressIndicator(myTask);
Platform.runLater(() -> imageBorder.setCenter(progressNode));
//called on fx thread
myTask.setOnSucceeded(succeeded -> {
showImage(file, myTask);
synchronized (DrawableUIBase.this) {
imageTask = null;
}
});
myTask.setOnFailed(failed -> {
Throwable exception = myTask.getException();
if (exception instanceof OutOfMemoryError
&& exception.getMessage().contains("Java heap space")) { //NON-NLS
showErrorNode(Bundle.DrawableUIBase_errorLabel_OOMText(), file);
} else {
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
}
synchronized (DrawableUIBase.this) {
imageTask = null;
}
});
myTask.setOnCancelled(cancelled -> {
synchronized (DrawableUIBase.this) {
imageTask = null;
}
imageView.setImage(null);
imageBorder.setCenter(null);
});
exec.execute(myTask);
return progressNode;
}
synchronized protected void disposeContent() {
if (imageTask != null) {
imageTask.cancel();
}
imageTask = null;
Platform.runLater(() -> {
imageView.setImage(null);
imageBorder.setCenter(null);
});
}
/**
*
* @param file the value of file
* @param imageTask the value of imageTask
*/
Node newProgressIndicator(final Task<?> imageTask) {
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
loadingProgressIndicator.progressProperty().bind(imageTask.progressProperty());
return loadingProgressIndicator;
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showImage(DrawableFile file, Task<Image> imageTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded()
try {
Image fxImage = imageTask.get();
if (nonNull(fxImage)) {
//we have non-null image show it
imageView.setImage(fxImage);
imageBorder.setCenter(imageView);
} else {
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
}
} catch (CancellationException ex) {
} catch (InterruptedException | ExecutionException ex) {
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void showErrorNode(String errorMessage, DrawableFile file) {
Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file));
VBox vBox = new VBox(10, new Label(errorMessage), createButton);
vBox.setAlignment(Pos.CENTER);
imageBorder.setCenter(vBox);
}
abstract Task<Image> newReadImageTask(DrawableFile file);
}