Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop

This commit is contained in:
Richard Cordovano 2016-01-18 09:13:42 -05:00
commit 000d56188b
8 changed files with 134 additions and 117 deletions

View File

@ -234,22 +234,29 @@ public class ImageUtils {
*/
public static boolean isGIF(AbstractFile file) {
try {
final FileTypeDetector fileTypeDetector = getFileTypeDetector();
if (nonNull(fileTypeDetector)) {
String fileType = fileTypeDetector.getFileType(file);
final FileTypeDetector myFileTypeDetector = getFileTypeDetector();
if (nonNull(myFileTypeDetector)) {
String fileType = myFileTypeDetector.getFileType(file);
return IMAGE_GIF_MIME.equalsIgnoreCase(fileType);
}
} catch (TskCoreException | FileTypeDetectorInitException ex) {
LOGGER.log(Level.WARNING, "Failed to get mime type with FileTypeDetector.", ex); //NOI18N
} catch (FileTypeDetectorInitException ex) {
LOGGER.log(Level.WARNING, "Failed to initialize FileTypeDetector.", ex); //NOI18N
} catch (TskCoreException ex) {
if (ex.getMessage().contains("An SQLException was provoked by the following failure: java.lang.InterruptedException")) {
LOGGER.log(Level.WARNING, "Mime type look up with FileTypeDetector was interupted."); //NOI18N}
return "gif".equalsIgnoreCase(file.getNameExtension()); //NOI18N
} else {
LOGGER.log(Level.SEVERE, "Failed to get mime type of " + getContentPathSafe(file) + " with FileTypeDetector.", ex); //NOI18N}
}
}
LOGGER.log(Level.WARNING, "Falling back on direct mime type check."); //NOI18N
LOGGER.log(Level.WARNING, "Falling back on direct mime type check for {0}.", getContentPathSafe(file)); //NOI18N
switch (file.isMimeType(GIF_MIME_SET)) {
case TRUE:
return true;
case UNDEFINED:
LOGGER.log(Level.WARNING, "Falling back on extension check."); //NOI18N
return "gif".equals(file.getNameExtension()); //NOI18N
return "gif".equalsIgnoreCase(file.getNameExtension()); //NOI18N
case FALSE:
default:
return false;
@ -660,6 +667,9 @@ public class ImageUtils {
if (isGIF(file)) {
return readImage();
}
if (isCancelled()) {
return null;
}
// If a thumbnail file is already saved locally, just read that.
if (cacheFile != null && cacheFile.exists()) {
@ -673,6 +683,9 @@ public class ImageUtils {
}
}
if (isCancelled()) {
return null;
}
//There was no correctly-sized cached thumbnail so make one.
BufferedImage thumbnail = null;
@ -722,6 +735,11 @@ public class ImageUtils {
throw e;
}
}
if (isCancelled()) {
return null;
}
updateProgress(-1, 1);
//if we got a valid thumbnail save it
@ -810,7 +828,9 @@ public class ImageUtils {
}
//fall through to default image reading code if there was an error
}
if (isCancelled()) {
return null;
}
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) {
throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
@ -834,9 +854,6 @@ public class ImageUtils {
param.setDestination(bufferedImage);
try {
bufferedImage = reader.read(0, param); //should always be same bufferedImage object
if (isCancelled()) {
return null;
}
} catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
@ -844,6 +861,9 @@ public class ImageUtils {
reader.removeIIOReadProgressListener(this);
reader.dispose();
}
if (isCancelled()) {
return null;
}
return SwingFXUtils.toFXImage(bufferedImage, null);
} else {
throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file));
@ -857,6 +877,7 @@ public class ImageUtils {
//update this task with the progress reported by ImageReader.read
updateProgress(percentageDone, 100);
if (isCancelled()) {
reader.removeIIOReadProgressListener(this);
reader.abort();
reader.dispose();
}

View File

@ -24,6 +24,8 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.stream.Collectors;
@ -89,7 +91,14 @@ import org.sleuthkit.datamodel.TskData;
* Connects different parts of ImageGallery together and is hub for flow of
* control.
*/
public final class ImageGalleryController {
public final class ImageGalleryController implements Executor {
private final Executor execDelegate = Executors.newSingleThreadExecutor();
@Override
public void execute(Runnable command) {
execDelegate.execute(command);
}
private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName());
@ -838,7 +847,7 @@ public final class ImageGalleryController {
/**
* Copy files from a newly added data source into the DB. Get all
* "drawable" files, based on extension. After ingest we use file type
* id module and if necessary jpeg signature matching to add/remove
* id module and if necessary jpeg/png signature matching to add/remove
* files
*/
@Override
@ -847,30 +856,28 @@ public final class ImageGalleryController {
updateMessage("prepopulating image/video database");
try {
String fsQuery = "";
String fsQuery = "(fs_obj_id IS NULL) "; //default clause
/*
* NOTE: Logical files currently (Apr '15) have a null value for
* fs_obj_id in DB. for them, we will not specify a fs_obj_id,
* which means we will grab files from another data source, but
* the drawable DB is smart enough to de-dupe them. For Images
* we can do better.
*/
if (dataSource instanceof Image) {
List<FileSystem> fileSystems = ((Image) dataSource).getFileSystems();
if (fileSystems.isEmpty() == false) {
if (fileSystems.isEmpty()) {
/*
* no filesystems, don't bother with the initial
* population, just catch things on file_done
* population, just sort things out on file_done events
*/
progressHandle.finish();
return;
}
String internal = fileSystems.stream()
//use this clause to only grab files from the newly added filesystems.
fsQuery = fileSystems.stream()
.map(fileSystem -> String.valueOf(fileSystem.getId()))
.collect(Collectors.joining(" OR fs_obj_id = "));
fsQuery = "(fs_obj_id = " + internal + ") "; //suffix
} else {
/*
* NOTE: Logical files currently (Apr '15) have a null value
* for fs_obj_id in DB. for them, we will not specify a
* fs_obj_id, which means we will grab files from another
* data source, but the drawable DB is smart enough to
* de-dupe them.
*/
fsQuery = "(fs_obj_id IS NULL) ";
.collect(Collectors.joining(" OR fs_obj_id = ", "(fs_obj_id = ", ") "));
}
final List<AbstractFile> files = getSleuthKitCase().findAllFilesWhere(fsQuery + " AND " + DRAWABLE_QUERY);

View File

@ -39,11 +39,11 @@ import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
@ -184,12 +184,7 @@ public enum ThumbnailCache {
public Task<Image> getThumbnailTask(DrawableFile<?> file) {
final Image thumbnail = cache.getIfPresent(file.getId());
if (thumbnail != null) {
return new Task<Image>() {
@Override
protected Image call() throws Exception {
return thumbnail;
}
};
return TaskUtils.taskFrom(() -> thumbnail);
}
final Task<Image> newGetThumbnailTask = ImageUtils.newGetThumbnailTask(file.getAbstractFile(), MAX_THUMBNAIL_SIZE, false);
newGetThumbnailTask.stateProperty().addListener((Observable observable) -> {
@ -198,7 +193,7 @@ public enum ThumbnailCache {
try {
cache.put(Long.MIN_VALUE, newGetThumbnailTask.get());
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
LOGGER.log(Level.SEVERE, "There was an exception even though thumbnail task succedded for. This should not be possible.", ex);
}
}
});

View File

@ -19,9 +19,9 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.ObjectExpression;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@ -36,10 +36,10 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
*/
public class NextUnseenGroup extends Action {
private static final Image END
= new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png"));
private static final Image ADVANCE
= new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.png"));
private static final Image END =
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png"));
private static final Image ADVANCE =
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.png"));
private static final String MARK_GROUP_SEEN = "Mark Group Seen";
private static final String NEXT_UNSEEN_GROUP = "Next Unseen group";
@ -53,25 +53,36 @@ public class NextUnseenGroup extends Action {
//TODO: do we need both these listeners?
controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> {
Platform.runLater(this::updateButton);
updateButton();
});
controller.getGroupManager().getUnSeenGroups().addListener((Observable observable) -> {
Platform.runLater(this::updateButton);
updateButton();
});
setEventHandler((ActionEvent t) -> {
//fx-thread
//if there is a group assigned to the view, mark it as seen
Optional.ofNullable(controller.viewState())
.map(ObjectExpression<GroupViewState>::getValue)
.map(GroupViewState::getGroup)
.ifPresent(group -> controller.getGroupManager().markGroupSeen(group, true));
controller.execute(new Task<Void>() {
if (false == controller.getGroupManager().getUnSeenGroups().isEmpty()) {
controller.advance(GroupViewState.tile(controller.getGroupManager().getUnSeenGroups().get(0)), true);
}
updateButton();
@Override
protected Void call() throws Exception {
if (false == controller.getGroupManager().getUnSeenGroups().isEmpty()) {
controller.advance(GroupViewState.tile(controller.getGroupManager().getUnSeenGroups().get(0)), true);
}
return null;
}
@Override
protected void succeeded() {
super.succeeded();
updateButton();
}
});
});
updateButton();

View File

@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.util.Objects;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.CacheHint;
@ -48,6 +50,13 @@ public class DrawableTile extends DrawableTileBase {
private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE);
private static final Logger LOGGER = Logger.getLogger(DrawableTile.class.getName());
private final ChangeListener<? super Long> lastSelectionListener = (observable, oldValue, newValue) -> {
try {
setEffect(Objects.equals(newValue, getFileID()) ? LAST_SELECTED_EFFECT : null);
} catch (java.lang.IllegalStateException ex) {
Logger.getLogger(DrawableTile.class.getName()).log(Level.WARNING, "Error displaying tile");
}
};
@FXML
@Override
@ -63,13 +72,7 @@ public class DrawableTile extends DrawableTileBase {
imageView.fitHeightProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
imageView.fitWidthProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
selectionModel.lastSelectedProperty().addListener((observable, oldValue, newValue) -> {
try {
setEffect(Objects.equals(newValue, getFileID()) ? LAST_SELECTED_EFFECT : null);
} catch (java.lang.IllegalStateException ex) {
Logger.getLogger(DrawableTile.class.getName()).log(Level.WARNING, "Error displaying tile");
}
});
selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener));
}
public DrawableTile(GroupPane gp, ImageGalleryController controller) {
@ -100,6 +103,4 @@ public class DrawableTile extends DrawableTileBase {
return getFile().map(AbstractContent::getName).orElse("");
}
}

View File

@ -25,7 +25,9 @@ import java.util.Collection;
import java.util.Optional;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@ -111,7 +113,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
@FXML
private ImageView hashHitImageView;
/**
* displays the icon representing follow up tag
*/
@ -148,7 +149,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
super(controller);
this.groupPane = groupPane;
selectionModel = controller.getSelectionModel();
selectionModel.getSelected().addListener((Observable observable) -> updateSelectionState());
selectionModel.getSelected().addListener(new WeakInvalidationListener(selectionListener));
//set up mouse listener
//TODO: split this between DrawableTile and SingleDrawableViewBase
@ -243,6 +244,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
}
});
}
private final InvalidationListener selectionListener = (Observable observable) -> updateSelectionState();
GroupPane getGroupPane() {
return groupPane;
@ -314,7 +316,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
getFile().ifPresent(file -> {
final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit();
final String text = getTextForLabel();
Platform.runLater(() -> {
@ -322,7 +324,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
fileTypeImageView.setVisible(isVideo);
hashHitImageView.setManaged(hasHashSetHits);
hashHitImageView.setVisible(hasHashSetHits);
nameLabel.setText(text);
nameLabel.setTooltip(new Tooltip(text));
});

View File

@ -157,7 +157,11 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
}
});
myTask.setOnCancelled(cancelled -> {
disposeContent();
synchronized (DrawableUIBase.this) {
imageTask = null;
}
imageView.setImage(null);
imageBorder.setCenter(null);
});
exec.execute(myTask);

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import java.util.Arrays;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
@ -108,32 +107,12 @@ final public class NavPanel extends TabPane {
sortByBox.setButtonCell(new TreeNodeComparators.ComparatorListCell());
sortByBox.setItems(FXCollections.observableArrayList(FXCollections.observableArrayList(TreeNodeComparators.values())));
sortByBox.getSelectionModel().select(TreeNodeComparators.HIT_COUNT);
sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> {
//user action ->jfx thread
resortHashTree();
});
sortByBox.getSelectionModel().selectedItemProperty().addListener(o -> resortHashTree());
navTree.setRoot(navTreeRoot);
navTreeRoot.setExpanded(true);
navTree.setCellFactory((TreeView<TreeNode> p) -> new GroupTreeCell());
navTree.setShowRoot(false);
navTree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
hashTree.setRoot(hashTreeRoot);
hashTreeRoot.setExpanded(true);
hashTree.setCellFactory((TreeView<TreeNode> p) -> new GroupTreeCell());
hashTree.setShowRoot(false);
hashTree.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
activeTreeProperty.addListener((Observable o) -> {
updateControllersGroup();
activeTreeProperty.get().getSelectionModel().selectedItemProperty().addListener((Observable o1) -> {
updateControllersGroup();
});
});
this.activeTreeProperty.set(navTree);
configureTree(navTree, navTreeRoot);
configureTree(hashTree, hashTreeRoot);
activeTreeProperty.set(navTree);
navTabPane.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends Tab> ov, Tab t, Tab t1) -> {
if (t1 == hashTab) {
activeTreeProperty.set(hashTree);
@ -143,7 +122,6 @@ final public class NavPanel extends TabPane {
});
controller.getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
while (change.next()) {
for (DrawableGroup g : change.getAddedSubList()) {
insertIntoNavTree(g);
@ -158,15 +136,23 @@ final public class NavPanel extends TabPane {
}
});
rebuildTrees();
controller.viewState().addListener((ObservableValue<? extends GroupViewState> observable, GroupViewState oldValue, GroupViewState newValue) -> {
if (newValue != null && newValue.getGroup() != null) {
Platform.runLater(() -> {
setFocusedGroup(newValue.getGroup());
});
setFocusedGroup(newValue.getGroup());
}
});
rebuildTrees();
}
@ThreadConfined(type = ThreadType.JFX)
private void configureTree(final TreeView<TreeNode> navTree1, final GroupTreeItem navTreeRoot1) {
navTree1.setRoot(navTreeRoot1);
navTreeRoot1.setExpanded(true);
navTree1.setCellFactory(treeView -> new GroupTreeCell());
navTree1.setShowRoot(false);
navTree1.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
navTree1.getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
}
private void rebuildTrees() {
@ -207,38 +193,28 @@ final public class NavPanel extends TabPane {
*/
@ThreadConfined(type = ThreadType.JFX)
private void setFocusedGroup(DrawableGroup grouping) {
final GroupTreeItem treeItemForGroup = ((GroupTreeItem) activeTreeProperty.get().getRoot()).getTreeItemForPath(groupingToPath(grouping));
if (treeItemForGroup != null) {
/*
* When we used to run the below code on the FX thread, it would get
* into infinite loops when the next group button was pressed
* quickly because the udpates became out of order and History could
* not keep track of what was current.
*
* Currently (4/2/15), this method is already on the FX thread, so
* it is OK.
*/
//Platform.runLater(() -> {
activeTreeProperty.get().getSelectionModel().select(treeItemForGroup);
int row = activeTreeProperty.get().getRow(treeItemForGroup);
if (row != -1) {
activeTreeProperty.get().scrollTo(row - 2); //put newly selected row 3 from the top
}
//}); //end Platform.runLater
Platform.runLater(() -> {
int row = activeTreeProperty.get().getRow(treeItemForGroup);
if (row != -1) {
activeTreeProperty.get().scrollTo(row - 2); //put newly selected row 3 from the top
}
});
}
}
private static List<String> groupingToPath(DrawableGroup g) {
if (g.groupKey.getAttribute() == DrawableAttribute.PATH) {
String path = g.groupKey.getValueDisplayName();
String[] cleanPathTokens = StringUtils.stripStart(path, "/").split("/");
return Arrays.asList(cleanPathTokens);
private List<String> groupingToPath(DrawableGroup g) {
if (g.groupKey.getAttribute() != DrawableAttribute.PATH
|| activeTreeProperty.get() == hashTree) {
String stripStart = StringUtils.strip(g.groupKey.getValueDisplayName(), "/");
return Arrays.asList(stripStart);
} else {
return Arrays.asList(g.groupKey.getValueDisplayName());
String path = g.groupKey.getValueDisplayName();
String[] cleanPathTokens = StringUtils.stripStart(path, "/").split("/");
return Arrays.asList(cleanPathTokens);
}
}