Merge branch 'develop' of github.com:sleuthkit/autopsy into 6332-annotationViewer

This commit is contained in:
Greg DiCristofaro 2020-05-08 10:46:06 -04:00
commit 3dce7f528b
34 changed files with 727 additions and 565 deletions

View File

@ -1563,11 +1563,12 @@ public class Case {
*
* This should not be called from the event dispatch thread (EDT)
*
* @param newTag new ContentTag added
* @param deletedTag Removed ContentTag
* @param newTag The added ContentTag.
* @param deletedTagList List of ContentTags that were removed as a result
* of the addition of newTag.
*/
public void notifyContentTagAdded(ContentTag newTag, ContentTag deletedTag) {
eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTag));
public void notifyContentTagAdded(ContentTag newTag, List<ContentTag> deletedTagList) {
eventPublisher.publish(new ContentTagAddedEvent(newTag, deletedTagList));
}
/**
@ -1627,11 +1628,12 @@ public class Case {
*
* This should not be called from the event dispatch thread (EDT)
*
* @param newTag new BlackboardArtifactTag added
* @param removedTag The BlackboardArtifactTag that was removed.
* @param newTag The added ContentTag.
* @param removedTagList List of ContentTags that were removed as a result
* of the addition of newTag.
*/
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, BlackboardArtifactTag removedTag) {
eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTag));
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList));
}
/**

View File

@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* Event sent when a black board artifact tag is added.
*/
@Immutable
public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArtifactTag> implements Serializable {
public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArtifactTag, DeletedBlackboardArtifactTagInfo> implements Serializable {
private static final long serialVersionUID = 1L;
@ -38,8 +40,8 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArt
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag);
}
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag, BlackboardArtifactTag removedTag) {
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTag != null ? new DeletedBlackboardArtifactTagInfo(removedTag) : null));
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag, List<BlackboardArtifactTag> removedTagList) {
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTagList != null ? getDeletedInfo(removedTagList) : null));
}
/**
@ -54,4 +56,24 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArt
BlackboardArtifactTag getTagByID() throws NoCurrentCaseException, TskCoreException {
return Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagByTagID(getTagID());
}
/**
* Create a list of DeletedContentTagInfo objects from a list of
* BlackboardArtifactTags.
*
* @param deletedTagList List of deleted ContentTags.
*
* @return List of DeletedContentTagInfo objects or empty list if
* deletedTagList was empty or null.
*/
private static List<DeletedBlackboardArtifactTagInfo> getDeletedInfo(List<BlackboardArtifactTag> deletedTagList) {
List<DeletedBlackboardArtifactTagInfo> deletedInfoList = new ArrayList<>();
if (deletedTagList != null) {
for (BlackboardArtifactTag tag : deletedTagList) {
deletedInfoList.add(new DeletedBlackboardArtifactTagInfo(tag));
}
}
return deletedInfoList;
}
}

View File

@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* An event that is fired when a ContentTag is added.
*/
@Immutable
public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements Serializable {
public class ContentTagAddedEvent extends TagAddedEvent<ContentTag, DeletedContentTagInfo> implements Serializable {
private static final long serialVersionUID = 1L;
@ -38,8 +40,8 @@ public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements S
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag);
}
public ContentTagAddedEvent(ContentTag newTag, ContentTag deletedTag) {
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, (deletedTag != null ? new DeletedContentTagInfo(deletedTag) : null));
public ContentTagAddedEvent(ContentTag newTag, List<ContentTag> deletedTagList) {
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, getDeletedInfo(deletedTagList));
}
/**
@ -50,7 +52,26 @@ public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> implements S
* @throws NoCurrentCaseException
* @throws TskCoreException
*/
@Override
ContentTag getTagByID() throws NoCurrentCaseException, TskCoreException {
return Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagByTagID(getTagID());
}
/**
* Create a list of DeletedContentTagInfo objects from a list of ContentTags.
*
* @param deletedTagList List of deleted ContentTags.
*
* @return List of DeletedContentTagInfo objects or empty list if deletedTagList was empty or null.
*/
private static List<DeletedContentTagInfo> getDeletedInfo(List<ContentTag> deletedTagList) {
List<DeletedContentTagInfo> deletedInfoList = new ArrayList<>();
if (deletedTagList != null) {
for (ContentTag tag : deletedTagList) {
deletedInfoList.add(new DeletedContentTagInfo(tag));
}
}
return deletedInfoList;
}
}

View File

@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.TagDeletedEvent.DeletedTagInfo;
@ -30,7 +32,7 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
* Base Class for events that are fired when a Tag is added
*/
abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Serializable {
abstract class TagAddedEvent<T extends Tag, V extends DeletedTagInfo<T>> extends AutopsyEvent implements Serializable {
private static final long serialVersionUID = 1L;
@ -40,6 +42,8 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
*/
private transient T tag;
private List<V> deletedTagInfoList;
/**
* The id of the tag that was added. This will be used to re-load the
* transient tag from the database.
@ -50,10 +54,19 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
this(propertyName, addedTag, null);
}
TagAddedEvent(String propertyName, T addedTag, DeletedTagInfo<T> deletedTagInfo) {
super(propertyName, deletedTagInfo, null);
/**
* Construct a TagAddedEvent.
*
* @param propertyName Name of property changing
* @param addedTag Instance of added tag.
* @param deletedTagInfoList List of tags deleted as a result of the
* addition of addedTag.
*/
TagAddedEvent(String propertyName, T addedTag, List<V> deletedTagInfoList) {
super(propertyName, deletedTagInfoList, null);
tag = addedTag;
tagID = addedTag.getId();
this.deletedTagInfoList = deletedTagInfoList;
}
/**
@ -96,6 +109,21 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
}
}
/**
* Returns the list of tags that were removed as a result of the addition
* of the T.
*
* @return A list of removed tags or null if no tags were removed.
*/
public List<V> getDeletedTags() {
return deletedTagInfoList != null ? Collections.unmodifiableList(deletedTagInfoList) : null;
}
@Override
public Object getOldValue() {
return getDeletedTags();
}
/**
* implementors should override this to lookup the appropriate kind of tag
* (Content/BlackBoardArtifact) during the lazy load of the transient tag

View File

@ -80,7 +80,7 @@ final class TagNameDefinition implements Comparable<TagNameDefinition> {
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_TWO_NAME, new TagNameDefinition(CATEGORY_TWO_NAME, "", TagName.HTML_COLOR.LIME, TskData.FileKnown.BAD));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_THREE_NAME, new TagNameDefinition(CATEGORY_THREE_NAME, "", TagName.HTML_COLOR.YELLOW, TskData.FileKnown.BAD));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FOUR_NAME, new TagNameDefinition(CATEGORY_FOUR_NAME, "", TagName.HTML_COLOR.PURPLE, TskData.FileKnown.UNKNOWN));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.SILVER, TskData.FileKnown.UNKNOWN));
PROJECT_VIC_TAG_DEFINITIONS.put(CATEGORY_FIVE_NAME, new TagNameDefinition(CATEGORY_FIVE_NAME, "", TagName.HTML_COLOR.FUCHSIA, TskData.FileKnown.UNKNOWN));
}
/**

View File

@ -481,7 +481,7 @@ public class TagsManager implements Closeable {
try {
Case currentCase = Case.getCurrentCaseThrows();
currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
currentCase.notifyContentTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
} catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", ex);
@ -701,7 +701,7 @@ public class TagsManager implements Closeable {
TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment);
try {
Case currentCase = Case.getCurrentCaseThrows();
currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags().get(0));
currentCase.notifyBlackBoardArtifactTagAdded(tagChange.getAddedTag(), tagChange.getRemovedTags().isEmpty() ? null : tagChange.getRemovedTags());
} catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", ex);
}

View File

@ -37,6 +37,7 @@ import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
@ -62,6 +63,11 @@ import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
import org.sleuthkit.datamodel.Account;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT;
import org.sleuthkit.datamodel.CommunicationsUtils;
/**
* Listen for ingest events and update entries in the Central Repository
@ -337,6 +343,94 @@ public class IngestEventsListener {
event = evt;
}
/**
* Automatically creates personas from all the TSK_CONTACT artifacts
* found in a data source.
*
* @param dataSource Data source that was just analyzed.
* @throws TskCoreException If there is any error getting contact
* artifacts from case database.
* @throws CentralRepoException If there is an error in creating
* personas in the Central Repo.
*/
private void autoGenerateContactPersonas(Content dataSource) throws TskCoreException, CentralRepoException {
Blackboard blackboard;
try {
blackboard = Case.getCurrentCaseThrows().getSleuthkitCase().getBlackboard();
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex);
return;
}
// get all TSK_CONTACT artifacts in this data source.
List<BlackboardArtifact> contactArtifacts = blackboard.getArtifacts(TSK_CONTACT.getTypeID(), dataSource.getId());
for (BlackboardArtifact artifact : contactArtifacts) {
BlackboardAttribute nameAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
String personaName = (nameAttr != null) ? nameAttr.getValueString() : null;
// Get phone number and email attributes.
BlackboardAttribute phoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER));
BlackboardAttribute homePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME));
BlackboardAttribute mobilePhoneAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE));
BlackboardAttribute emailAttr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL));
Persona persona = personaFromContactAttribute(null, Account.Type.PHONE, phoneAttr, personaName);
persona = personaFromContactAttribute(persona, Account.Type.PHONE, homePhoneAttr, personaName);
persona = personaFromContactAttribute(persona, Account.Type.PHONE, mobilePhoneAttr, personaName);
personaFromContactAttribute(persona, Account.Type.EMAIL, emailAttr, personaName);
}
}
/**
* Gets central repo account for the given attribute for a TSK_CONTACT
* artifact. Associates the given persona with that account. Creates a
* Persona, if one isn't provided.
*
* @param persona Persona to associate with the account. May be null, in
* which case a persona is created first.
* @param accountType Account type of account to be associated.
* @param attribute Attribute form which get the account id.
* @param personaName Persona name, if a persona needs to be created.
* @return Persona created or associated with the account.
*
* @throws TskCoreException If there is an error in normalizing the
* account id.
* @throws CentralRepoException If there is an erorr is getting the
* account or associating the persona with it.
*/
private Persona personaFromContactAttribute(Persona persona, Account.Type accountType, BlackboardAttribute attribute, String personaName) throws CentralRepoException, TskCoreException {
Persona personaToReturn = persona;
if (attribute != null) {
String accountId = attribute.getValueString();
if (CommunicationsUtils.isValidAccountId(accountType, accountId)) {
if (accountType == Account.Type.PHONE) {
accountId = CommunicationsUtils.normalizePhoneNum(accountId);
} else if (accountType == Account.Type.EMAIL) {
accountId = CommunicationsUtils.normalizeEmailAddress(accountId);
}
CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountType.getTypeName());
CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountId);
PersonaAccount personaAccount;
// If persona doesnt exist, create one
if (persona == null) {
personaAccount = Persona.createPersonaForAccount(personaName, "Auto generated contact persona", Persona.PersonaStatus.UNKNOWN, crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
personaToReturn = personaAccount.getPersona();
} else {
persona.addAccountToPersona(crAccount, "Found in contact book entry", Persona.Confidence.DERIVED);
}
}
}
return personaToReturn;
}
@Override
public void run() {
// clear the tracker to reduce memory usage
@ -411,6 +505,8 @@ public class IngestEventsListener {
correlationDataSource.setSha256(imageSha256Hash);
}
}
// automatically generate persona from contact artifacts.
autoGenerateContactPersonas(dataSource);
}
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, String.format(

View File

@ -257,6 +257,6 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter
return waypointMostRecent;
}
return null;
return -1L;
}
}

View File

@ -658,6 +658,17 @@ public final class EventsModel {
return postTagsDeleted(updatedEventIDs);
}
/**
* Updates the events model for a data source added event.
*
* @throws TskCoreException If there is an error reading model data from the
* case database.
*/
synchronized void handleDataSourceAdded() throws TskCoreException {
populateDataSourcesCache();
invalidateCaches(null);
}
/**
* Updates the events model for an artifact tag deleted event and publishes
* a tag deleted event via the model's event bus.
@ -782,7 +793,6 @@ public final class EventsModel {
* @throws TskCoreException
*/
public synchronized void invalidateCaches(Collection<Long> updatedEventIDs) throws TskCoreException {
populateDataSourcesCache();
minEventTimeCache.invalidateAll();
maxEventTimeCache.invalidateAll();
idsToEventsCache.invalidateAll(emptyIfNull(updatedEventIDs));

View File

@ -783,7 +783,7 @@ public class TimeLineController {
break;
case DATA_SOURCE_ADDED:
future = executor.submit(() -> {
filteredEvents.invalidateCaches(null);
filteredEvents.handleDataSourceAdded();
return null;
});
break;

View File

@ -79,6 +79,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -108,6 +109,8 @@ public final class ImageGalleryController {
Case.Events.DATA_SOURCE_DELETED
);
private static final String CATEGORY_TAG_SET_PREFIX = "Project VIC";
/*
* There is an image gallery controller per case. It is created during the
* opening of case resources and destroyed during the closing of case
@ -228,14 +231,16 @@ public final class ImageGalleryController {
void startUp() throws TskCoreException {
selectionModel = new FileIDSelectionModel(this);
thumbnailCache = new ThumbnailCache(this);
TagSet categoryTagSet = getCategoryTagSet();
/*
* TODO (JIRA-5212): The next two lines need to be executed in this
* order. Why? This suggests there is some inappropriate coupling
* between the DrawableDB and GroupManager classes.
*/
groupManager = new GroupManager(this);
drawableDB = DrawableDB.getDrawableDB(this);
categoryManager = new CategoryManager(this);
drawableDB = DrawableDB.getDrawableDB(this, categoryTagSet);
categoryManager = new CategoryManager(this, categoryTagSet);
tagsManager = new DrawableTagsManager(this);
tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager);
@ -721,6 +726,28 @@ public final class ImageGalleryController {
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
}
/**
* Returns the TagSet with the image gallery categories.
*
* @return Category TagSet.
*
* @throws TskCoreException
*/
private TagSet getCategoryTagSet() throws TskCoreException {
List<TagSet> tagSetList = getCaseDatabase().getTaggingManager().getTagSets();
if (tagSetList != null && !tagSetList.isEmpty()) {
for (TagSet set : tagSetList) {
if (set.getName().startsWith(CATEGORY_TAG_SET_PREFIX)) {
return set;
}
}
// If we get to here the Project VIC Test set wasn't found;
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
} else {
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
}
}
/**
* A listener for ingest module application events.
*/

View File

@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A task that updates one drawable file in the drawables database.
* A task that updates one drawable file in the drawable database.
*/
class UpdateDrawableFileTask extends DrawableDbTask {

View File

@ -19,6 +19,9 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableMap;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -27,9 +30,10 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.collections.ObservableSet;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Node;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javax.annotation.Nonnull;
@ -41,9 +45,7 @@ import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.DrawableDbTask;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
@ -51,6 +53,7 @@ import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import javafx.scene.image.ImageView;
/**
* An action that associates a drawable file with a Project Vic category.
@ -62,24 +65,24 @@ public class CategorizeAction extends Action {
private final ImageGalleryController controller;
private final UndoRedoManager undoManager;
private final DhsImageCategory cat;
private final Set<Long> selectedFileIDs;
private final Boolean createUndo;
private final TagName tagName;
public CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set<Long> selectedFileIDs) {
this(controller, cat, selectedFileIDs, true);
public CategorizeAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
this(controller, tagName, selectedFileIDs, true);
}
private CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set<Long> selectedFileIDs, Boolean createUndo) {
super(cat.getDisplayName());
private CategorizeAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs, Boolean createUndo) {
super(tagName.getDisplayName());
this.controller = controller;
this.undoManager = controller.getUndoManager();
this.cat = cat;
this.selectedFileIDs = selectedFileIDs;
this.createUndo = createUndo;
setGraphic(cat.getGraphic());
this.tagName = tagName;
setGraphic(getGraphic(tagName));
setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs));
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(getCategoryNumberFromTagName(tagName))));
}
static public Menu getCategoriesMenu(ImageGalleryController controller) {
@ -87,8 +90,18 @@ public class CategorizeAction extends Action {
}
final void addCatToFiles(Set<Long> ids) {
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS
controller.queueDBTask(new CategorizeDrawableFileTask(ids, cat, createUndo));
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), tagName.getDisplayName()}); //NON-NLS
controller.queueDBTask(new CategorizeDrawableFileTask(ids, tagName, createUndo));
}
private String getCategoryNumberFromTagName(TagName tagName) {
String displayName = tagName.getDisplayName();
if (displayName.contains("CAT")) {
String[] split = displayName.split(":");
split = split[0].split("-");
return split[1];
}
return "";
}
/**
@ -104,8 +117,8 @@ public class CategorizeAction extends Action {
// Each category get an item in the sub-menu. Selecting one of these menu items adds
// a tag with the associated category.
for (final DhsImageCategory cat : DhsImageCategory.values()) {
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
for (TagName tagName : controller.getCategoryManager().getCategories()) {
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, tagName, selected));
getItems().add(categoryItem);
}
}
@ -124,54 +137,39 @@ public class CategorizeAction extends Action {
final Set<Long> fileIDs;
final boolean createUndo;
final DhsImageCategory cat;
final TagName catTagName;
CategorizeDrawableFileTask(Set<Long> fileIDs, @Nonnull DhsImageCategory cat, boolean createUndo) {
CategorizeDrawableFileTask(Set<Long> fileIDs, @Nonnull TagName catTagName, boolean createUndo) {
super();
this.fileIDs = fileIDs;
java.util.Objects.requireNonNull(cat);
this.cat = cat;
java.util.Objects.requireNonNull(catTagName);
this.catTagName = catTagName;
this.createUndo = createUndo;
}
@Override
public void run() {
final DrawableTagsManager tagsManager = controller.getTagsManager();
final CategoryManager categoryManager = controller.getCategoryManager();
Map<Long, DhsImageCategory> oldCats = new HashMap<>();
TagName tagName = categoryManager.getTagName(cat);
Map<Long, TagName> oldCats = new HashMap<>();
for (long fileID : fileIDs) {
try {
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
if (createUndo) {
DhsImageCategory oldCat = file.getCategory(); //drawable db access
TagName oldCatTagName = categoryManager.getTagName(oldCat);
if (false == tagName.equals(oldCatTagName)) {
oldCats.put(fileID, oldCat);
TagName oldCatTagName = file.getCategory(); //drawable db access
if (false == catTagName.equals(oldCatTagName)) {
oldCats.put(fileID, oldCatTagName);
}
}
final List<ContentTag> fileTags = tagsManager.getContentTags(file);
if (tagName == categoryManager.getTagName(DhsImageCategory.ZERO)) {
// delete all cat tags for cat-0
fileTags.stream()
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
.forEach((ct) -> {
try {
tagsManager.deleteContentTag(ct);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error removing old categories result", ex); //NON-NLS
}
});
} else {
//add cat tag if no existing cat tag for that cat
if (fileTags.stream()
.map(Tag::getName)
.filter(tagName::equals)
.collect(Collectors.toList()).isEmpty()) {
tagsManager.addContentTag(file, tagName, "");
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error categorizing result", ex); //NON-NLS
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
@ -183,7 +181,7 @@ public class CategorizeAction extends Action {
}
if (createUndo && oldCats.isEmpty() == false) {
undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
undoManager.addToUndo(new CategorizationChange(controller, catTagName, oldCats));
}
}
}
@ -194,14 +192,14 @@ public class CategorizeAction extends Action {
@Immutable
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
private final DhsImageCategory newCategory;
private final ImmutableMap<Long, DhsImageCategory> oldCategories;
private final TagName newTagNameCategory;
private final ImmutableMap<Long, TagName> oldTagNameCategories;
private final ImageGalleryController controller;
CategorizationChange(ImageGalleryController controller, DhsImageCategory newCategory, Map<Long, DhsImageCategory> oldCategories) {
CategorizationChange(ImageGalleryController controller, TagName newTagNameCategory, Map<Long, TagName> oldTagNameCategories) {
this.controller = controller;
this.newCategory = newCategory;
this.oldCategories = ImmutableMap.copyOf(oldCategories);
this.newTagNameCategory = newTagNameCategory;
this.oldTagNameCategories = ImmutableMap.copyOf(oldTagNameCategories);
}
/**
@ -210,7 +208,7 @@ public class CategorizeAction extends Action {
*/
@Override
public void run() {
new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false)
new CategorizeAction(controller, newTagNameCategory, this.oldTagNameCategories.keySet(), false)
.handle(null);
}
@ -221,10 +219,42 @@ public class CategorizeAction extends Action {
@Override
public void undo() {
for (Map.Entry<Long, DhsImageCategory> entry : oldCategories.entrySet()) {
for (Map.Entry<Long, TagName> entry : oldTagNameCategories.entrySet()) {
new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
.handle(null);
}
}
}
/**
* Create an BufferedImage to use as the icon for the given TagName.
*
* @param tagName The category TagName.
*
* @return TagName Icon BufferedImage.
*/
private BufferedImage getImageForTagName(TagName tagName) {
BufferedImage off_image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = off_image.createGraphics();
g2.setColor(java.awt.Color.decode(tagName.getColor().getRgbValue()));
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.BLACK);
g2.drawRect(0, 0, 16, 16);
return off_image;
}
/**
* Returns a Node which is a ImageView of the icon for the given TagName.
*
* @param tagname
*
* @return Node for use as the TagName menu item graphic.
*/
private Node getGraphic(TagName tagname) {
BufferedImage buff_image = getImageForTagName(tagname);
return new ImageView(SwingFXUtils.toFXImage(buff_image, null));
}
}

View File

@ -25,6 +25,7 @@ import java.util.logging.Level;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
@ -37,9 +38,9 @@ import javafx.scene.layout.VBox;
import static org.apache.commons.lang.ObjectUtils.notEqual;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -50,7 +51,7 @@ public class CategorizeGroupAction extends CategorizeAction {
private final static Logger LOGGER = Logger.getLogger(CategorizeGroupAction.class.getName());
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
public CategorizeGroupAction(TagName newCat, ImageGalleryController controller) {
super(controller, newCat, null);
setEventHandler(actionEvent -> {
controller.getViewState().getGroup().ifPresent(group -> {
@ -60,12 +61,12 @@ public class CategorizeGroupAction extends CategorizeAction {
//if they have preveiously disabled the warning, just go ahead and apply categories.
addCatToFiles(ImmutableSet.copyOf(fileIDs));
} else {
final Map<DhsImageCategory, Long> catCountMap = new HashMap<>();
final Map<TagName, Long> catCountMap = new HashMap<>();
for (Long fileID : fileIDs) {
try {
DhsImageCategory category = controller.getFileFromID(fileID).getCategory();
if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
TagName category = controller.getFileFromID(fileID).getCategory();
if (category != null && newCat.equals(category) == false) {
catCountMap.merge(category, 1L, Long::sum);
}
} catch (TskCoreException ex) {
@ -90,18 +91,19 @@ public class CategorizeGroupAction extends CategorizeAction {
"CategorizeGroupAction.fileCountMessage={0} with {1}",
"CategorizeGroupAction.dontShowAgain=Don't show this message again",
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
private void showConfirmationDialog(final Map<DhsImageCategory, Long> catCountMap, DhsImageCategory newCat, ObservableList<Long> fileIDs) {
private void showConfirmationDialog(final Map<TagName, Long> catCountMap, TagName newCat, ObservableList<Long> fileIDs) {
ButtonType categorizeButtonType
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
VBox textFlow = new VBox();
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
if (entry.getValue() > 0
for (Map.Entry<TagName, Long> entry : catCountMap.entrySet()) {
if (entry != null && entry.getValue() > 0
&& notEqual(entry.getKey(), newCat)) {
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
entry.getKey().getGraphic());
getGraphic(entry.getKey()));
label.setContentDisplay(ContentDisplay.RIGHT);
textFlow.getChildren().add(label);
}
@ -127,4 +129,8 @@ public class CategorizeGroupAction extends CategorizeAction {
}
});
}
public Node getGraphic(TagName tagName) {
return null;
}
}

View File

@ -19,14 +19,14 @@
package org.sleuthkit.autopsy.imagegallery.actions;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.datamodel.TagName;
/**
*
*/
public class CategorizeSelectedFilesAction extends CategorizeAction {
public CategorizeSelectedFilesAction(DhsImageCategory cat, ImageGalleryController controller) {
public CategorizeSelectedFilesAction(TagName cat, ImageGalleryController controller) {
super(controller, cat, null);
setEventHandler(actionEvent ->
addCatToFiles(controller.getSelectionModel().getSelected())

View File

@ -24,8 +24,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
import java.util.logging.Level;
@ -33,11 +36,13 @@ import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -56,8 +61,6 @@ public class CategoryManager {
private static final Logger LOGGER = Logger.getLogger(CategoryManager.class.getName());
private final ImageGalleryController controller;
/**
* the DrawableDB that backs the category counts cache. The counts are
* initialized from this, and the counting of CAT-0 is always delegated to
@ -65,6 +68,8 @@ public class CategoryManager {
*/
private final DrawableDB drawableDb;
private final TagSet categoryTagSet;
/**
* Used to distribute CategoryChangeEvents
*/
@ -79,32 +84,20 @@ public class CategoryManager {
* the count related methods go through this cache, which loads initial
* values from the database if needed.
*/
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts
private final LoadingCache<TagName, LongAdder> categoryCounts
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
/**
* cached TagNames corresponding to Categories, looked up from
* autopsyTagManager at initial request or if invalidated by case change.
*/
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
@Override
public TagName load(DhsImageCategory cat) throws TskCoreException {
return getController().getTagsManager().getTagName(cat);
}
});
public CategoryManager(ImageGalleryController controller) {
this.controller = controller;
public CategoryManager(ImageGalleryController controller, TagSet categoryTagSet) throws TskCoreException {
this.drawableDb = controller.getDrawablesDatabase();
this.categoryTagSet = categoryTagSet;
}
private ImageGalleryController getController() {
return controller;
public List<TagName> getCategories() {
return Collections.unmodifiableList(getSortedTagNames(categoryTagSet.getTagNames()));
}
synchronized public void invalidateCaches() {
categoryCounts.invalidateAll();
catTagNameMap.invalidateAll();
fireChange(Collections.emptyList(), null);
}
@ -115,16 +108,8 @@ public class CategoryManager {
*
* @return the number of files with the given Category
*/
synchronized public long getCategoryCount(DhsImageCategory cat) {
if (cat == DhsImageCategory.ZERO) {
// Keeping track of the uncategorized files is a bit tricky while ingest
// is going on, so always use the list of file IDs we already have along with the
// other category counts instead of trying to track it separately.
long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE);
return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount;
} else {
return categoryCounts.getUnchecked(cat).sum();
}
synchronized public long getCategoryCount(TagName tagName) {
return categoryCounts.getUnchecked(tagName).sum();
}
/**
@ -133,10 +118,8 @@ public class CategoryManager {
*
* @param cat the Category to increment
*/
synchronized public void incrementCategoryCount(DhsImageCategory cat) {
if (cat != DhsImageCategory.ZERO) {
categoryCounts.getUnchecked(cat).increment();
}
synchronized public void incrementCategoryCount(TagName tagName) {
categoryCounts.getUnchecked(tagName).increment();
}
/**
@ -145,10 +128,8 @@ public class CategoryManager {
*
* @param cat the Category to decrement
*/
synchronized public void decrementCategoryCount(DhsImageCategory cat) {
if (cat != DhsImageCategory.ZERO) {
categoryCounts.getUnchecked(cat).decrement();
}
synchronized public void decrementCategoryCount(TagName tagName) {
categoryCounts.getUnchecked(tagName).decrement();
}
/**
@ -161,14 +142,14 @@ public class CategoryManager {
* @return a LongAdder whose value is set to the number of file with the
* given Category
*/
synchronized private LongAdder getCategoryCountHelper(DhsImageCategory cat) {
synchronized private LongAdder getCategoryCountHelper(TagName cat) {
LongAdder longAdder = new LongAdder();
longAdder.decrement();
try {
longAdder.add(drawableDb.getCategoryCount(cat));
longAdder.increment();
} catch (IllegalStateException ex) {
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
LOGGER.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
}
return longAdder;
}
@ -178,8 +159,8 @@ public class CategoryManager {
*
* @param fileIDs
*/
public void fireChange(Collection<Long> fileIDs, DhsImageCategory newCategory) {
categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory));
public void fireChange(Collection<Long> fileIDs, TagName tagName) {
categoryEventBus.post(new CategoryChangeEvent(fileIDs, tagName));
}
/**
@ -216,68 +197,66 @@ public class CategoryManager {
}
/**
* get the TagName used to store this Category in the main autopsy db.
* Returns true if the given TagName is a category tag.
*
* @return the TagName used for this Category
* @param tName TagName
*
* @return True if tName is a category tag.
*/
synchronized public TagName getTagName(DhsImageCategory cat) {
return catTagNameMap.getUnchecked(cat);
public boolean isCategoryTagName(TagName tName) {
return categoryTagSet.getTagNames().contains(tName);
}
/**
* Returns true if the given TagName is not a category tag.
*
* Keep for use in location were a reference to this function is passed.
*
* @param tName TagName
*
* @return True if the given tName is not a category tag.
*/
public boolean isNotCategoryTagName(TagName tName) {
return !isCategoryTagName(tName);
}
public static DhsImageCategory categoryFromTagName(TagName tagName) {
return DhsImageCategory.fromDisplayName(tagName.getDisplayName());
}
public static boolean isCategoryTagName(TagName tName) {
return DhsImageCategory.isCategoryName(tName.getDisplayName());
}
public static boolean isNotCategoryTagName(TagName tName) {
return DhsImageCategory.isNotCategoryName(tName.getDisplayName());
/**
* Returns the category tag set.
*
* @return
*/
TagSet getCategorySet() {
return categoryTagSet;
}
@Subscribe
public void handleTagAdded(ContentTagAddedEvent event) {
final ContentTag addedTag = event.getAddedTag();
if (isCategoryTagName(addedTag.getName())) {
final DrawableTagsManager tagsManager = controller.getTagsManager();
try {
//remove old category tag(s) if necessary
for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
if (ct.getId() != addedTag.getId()
&& CategoryManager.isCategoryTagName(ct.getName())) {
try {
tagsManager.deleteContentTag(ct);
} catch (TskCoreException tskException) {
LOGGER.log(Level.SEVERE, "Failed to delete content tag. Unable to maintain categories in a consistent state.", tskException); //NON-NLS
break;
List<DeletedContentTagInfo> removedTags = event.getDeletedTags();
if (removedTags != null) {
for (DeletedContentTagInfo tagInfo : removedTags) {
handleDeletedInfo(tagInfo);
}
}
}
} catch (TskCoreException tskException) {
LOGGER.log(Level.SEVERE, "Failed to get content tags for content. Unable to maintain category in a consistent state.", tskException); //NON-NLS
}
DhsImageCategory newCat = CategoryManager.categoryFromTagName(addedTag.getName());
if (newCat != DhsImageCategory.ZERO) {
incrementCategoryCount(newCat);
}
fireChange(Collections.singleton(addedTag.getContent().getId()), newCat);
if (isCategoryTagName(addedTag.getName())) {
incrementCategoryCount(addedTag.getName());
fireChange(Collections.singleton(addedTag.getContent().getId()), addedTag.getName());
}
}
@Subscribe
public void handleTagDeleted(ContentTagDeletedEvent event) {
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
handleDeletedInfo(deletedTagInfo);
}
private void handleDeletedInfo(DeletedContentTagInfo deletedTagInfo) {
TagName tagName = deletedTagInfo.getName();
if (isCategoryTagName(tagName)) {
DhsImageCategory deletedCat = CategoryManager.categoryFromTagName(tagName);
if (deletedCat != DhsImageCategory.ZERO) {
decrementCategoryCount(deletedCat);
}
decrementCategoryCount(tagName);
fireChange(Collections.singleton(deletedTagInfo.getContentID()), null);
}
}
@ -290,16 +269,16 @@ public class CategoryManager {
public static class CategoryChangeEvent {
private final ImmutableSet<Long> fileIDs;
private final DhsImageCategory newCategory;
private final TagName tagName;
public CategoryChangeEvent(Collection<Long> fileIDs, DhsImageCategory newCategory) {
public CategoryChangeEvent(Collection<Long> fileIDs, TagName tagName) {
super();
this.fileIDs = ImmutableSet.copyOf(fileIDs);
this.newCategory = newCategory;
this.tagName = tagName;
}
public DhsImageCategory getNewCategory() {
return newCategory;
public TagName getNewCategory() {
return tagName;
}
/**
@ -309,4 +288,18 @@ public class CategoryManager {
return fileIDs;
}
}
private List<TagName> getSortedTagNames(List<TagName> tagNames) {
Comparator<TagName> compareByDisplayName = new Comparator<TagName>() {
@Override
public int compare(TagName tagName1, TagName tagName2) {
return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
}
};
List<TagName> sortedTagNames = new ArrayList<>(tagNames);
sortedTagNames.sort(compareByDisplayName);
return sortedTagNames;
}
}

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@ -89,15 +88,17 @@ public class DrawableAttribute<T extends Comparable<T>> {
* //TODO: this has lead to awkward hard to maintain code, and little
* advantage. move categories into DrawableDB?
*/
public final static DrawableAttribute<DhsImageCategory> CATEGORY
= new DrawableAttribute<DhsImageCategory>(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
public final static DrawableAttribute<TagName> CATEGORY
= new DrawableAttribute<TagName>(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(),
false,
"category-icon.png", //NON-NLS
f -> Collections.singleton(f.getCategory())) {
@Override
public Node getGraphicForValue(DhsImageCategory val) {
return val.getGraphic();
public Node getGraphicForValue(TagName val) {
return null;
//return val.getGraphic();
}
};
@ -235,9 +236,13 @@ public class DrawableAttribute<T extends Comparable<T>> {
.filter(value -> (value != null && value.toString().isEmpty() == false))
.collect(Collectors.toSet());
} catch (Exception ex) {
/* There is a catch-all here because the code in the try block executes third-party
library calls that throw unchecked exceptions. See JIRA-5144, where an IllegalStateException
was thrown because a file's MIME type was incorrectly identified as a picture type. */
/*
* There is a catch-all here because the code in the try block
* executes third-party library calls that throw unchecked
* exceptions. See JIRA-5144, where an IllegalStateException was
* thrown because a file's MIME type was incorrectly identified as a
* picture type.
*/
logger.log(Level.WARNING, "Exception while getting image attributes", ex); //NON-NLS
return Collections.emptySet();
}

View File

@ -52,11 +52,9 @@ import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
@ -80,6 +78,7 @@ import org.sleuthkit.datamodel.TskDataException;
import org.sleuthkit.datamodel.VersionNumber;
import org.sqlite.SQLiteJDBCLoader;
import java.util.stream.Collectors;
import org.sleuthkit.datamodel.TagSet;
/**
* Provides access to the image gallery database and selected tables in the case
@ -250,7 +249,7 @@ public final class DrawableDB {
* could not be correctly initialized for Image
* Gallery use.
*/
private DrawableDB(Path dbPath, ImageGalleryController controller) throws IOException, SQLException, TskCoreException {
private DrawableDB(Path dbPath, ImageGalleryController controller, TagSet standardCategories) throws IOException, SQLException, TskCoreException {
this.dbPath = dbPath;
this.controller = controller;
caseDb = this.controller.getCaseDatabase();
@ -259,7 +258,7 @@ public final class DrawableDB {
dbWriteLock();
try {
con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups() || !removeDeletedDataSources() || !initializeImageList()) {
if (!initializeDBSchema() || !upgradeDBSchema() || !prepareStatements() || !initializeStandardGroups(standardCategories) || !removeDeletedDataSources() || !initializeImageList()) {
close();
throw new TskCoreException("Failed to initialize drawables database for Image Gallery use"); //NON-NLS
}
@ -297,12 +296,13 @@ public final class DrawableDB {
}
}
private boolean initializeStandardGroups() {
private boolean initializeStandardGroups(TagSet standardCategories) {
CaseDbTransaction caseDbTransaction = null;
try {
caseDbTransaction = caseDb.beginTransaction();
for (DhsImageCategory cat : DhsImageCategory.values()) {
insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
for(TagName tagName: standardCategories.getTagNames()) {
insertGroup(tagName.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction);
}
caseDbTransaction.commit();
return true;
@ -466,7 +466,7 @@ public final class DrawableDB {
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public static DrawableDB getDrawableDB(ImageGalleryController controller) throws TskCoreException {
public static DrawableDB getDrawableDB(ImageGalleryController controller, TagSet standardCategories) throws TskCoreException {
Path dbPath = ImageGalleryModule.getModuleOutputDir(controller.getCase()).resolve("drawable.db");
try {
deleteDatabaseIfOlderVersion(dbPath);
@ -477,7 +477,7 @@ public final class DrawableDB {
}
try {
return new DrawableDB(dbPath, controller);
return new DrawableDB(dbPath, controller, standardCategories);
} catch (IOException ex) {
throw new TskCoreException("Failed to create drawables database directory", ex); //NON-NLS
} catch (SQLException ex) {
@ -2068,7 +2068,7 @@ public final class DrawableDB {
case MIME_TYPE:
return groupManager.getFileIDsWithMimeType((String) groupKey.getValue());
case CATEGORY:
return groupManager.getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
return groupManager.getFileIDsWithCategory((TagName) groupKey.getValue());
case TAGS:
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
}
@ -2269,9 +2269,8 @@ public final class DrawableDB {
*
* @return the number of the with the given category
*/
public long getCategoryCount(DhsImageCategory cat) {
public long getCategoryCount(TagName tagName) {
try {
TagName tagName = controller.getTagsManager().getTagName(cat);
if (nonNull(tagName)) {
return caseDb.getContentTagsByTagName(tagName).stream()
.map(ContentTag::getContent)
@ -2280,7 +2279,7 @@ public final class DrawableDB {
.count();
}
} catch (IllegalStateException ex) {
logger.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
logger.log(Level.WARNING, "Case closed while getting files", ex); //NON-NLS
} catch (TskCoreException ex1) {
logger.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); //NON-NLS
}
@ -2314,7 +2313,6 @@ public final class DrawableDB {
DrawableTagsManager tagsManager = controller.getTagsManager();
String catTagNameIDs = tagsManager.getCategoryTagNames().stream()
.filter(tagName -> notEqual(tagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName()))
.map(TagName::getId)
.map(Object::toString)
.collect(Collectors.joining(",", "(", ")"));

View File

@ -40,8 +40,8 @@ import org.apache.commons.lang3.text.WordUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -94,15 +94,19 @@ public abstract class DrawableFile {
private final SimpleBooleanProperty analyzed;
private final SimpleObjectProperty<DhsImageCategory> category = new SimpleObjectProperty<>(null);
private final SimpleObjectProperty<TagName> categoryTagName = new SimpleObjectProperty<>(null);
private String make;
private String model;
private final CategoryManager categoryManager;
protected DrawableFile(AbstractFile file, Boolean analyzed) {
this.analyzed = new SimpleBooleanProperty(analyzed);
this.file = file;
categoryManager = ImageGalleryController.getController(Case.getCurrentCase()).getCategoryManager();
}
public abstract boolean isVideo();
@ -229,32 +233,30 @@ public abstract class DrawableFile {
return "";
}
public void setCategory(DhsImageCategory category) {
categoryProperty().set(category);
}
public DhsImageCategory getCategory() {
public TagName getCategory() {
updateCategory();
return category.get();
return categoryTagName.get();
}
public SimpleObjectProperty<DhsImageCategory> categoryProperty() {
return category;
public SimpleObjectProperty<TagName> categoryProperty() {
return categoryTagName;
}
/**
* set the category property to the most severe one found
* Update the category property.
*/
private void updateCategory() {
try {
category.set(getContentTags().stream()
.map(Tag::getName).filter(CategoryManager::isCategoryTagName)
.map(TagName::getDisplayName)
.map(DhsImageCategory::fromDisplayName)
.sorted().findFirst() //sort by severity and take the first
.orElse(DhsImageCategory.ZERO)
);
List<ContentTag> contentTags = getContentTags();
TagName tag = null;
for (ContentTag ct : contentTags) {
TagName tagName = ct.getName();
if (categoryManager.isCategoryTagName(tagName)) {
tag = tagName;
break;
}
}
categoryTagName.set(tag);
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "problem looking up category for " + this.getContentPathSafe(), ex); //NON-NLS
} catch (IllegalStateException ex) {

View File

@ -20,10 +20,11 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@ -33,7 +34,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
@ -54,10 +54,16 @@ public final class DrawableTagsManager {
private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
private final TagsManager autopsyTagsManager;
/** The tag name corresponding to the "built-in" tag "Follow Up" */
/**
* The tag name corresponding to the "built-in" tag "Follow Up"
*/
private final TagName followUpTagName;
private final TagName bookmarkTagName;
private final ImageGalleryController controller;
private final Comparator<TagName> compareByDisplayName;
/**
* Used to distribute TagsChangeEvents
*/
@ -74,6 +80,14 @@ public final class DrawableTagsManager {
this.autopsyTagsManager = controller.getCase().getServices().getTagsManager();
followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp());
bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark());
this.controller = controller;
compareByDisplayName = new Comparator<TagName>() {
@Override
public int compare(TagName tagName1, TagName tagName2) {
return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
}
};
}
/**
@ -129,25 +143,26 @@ public final class DrawableTagsManager {
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public List<TagName> getNonCategoryTagNames() throws TskCoreException {
return autopsyTagsManager.getAllTagNames().stream()
.filter(CategoryManager::isNotCategoryTagName)
.distinct().sorted()
.collect(Collectors.toList());
List<TagName> nonCategoryTagNames = new ArrayList<>();
List<TagName> allTags = autopsyTagsManager.getAllTagNames();
for (TagName tag : allTags) {
if (controller.getCategoryManager().isNotCategoryTagName(tag)) {
nonCategoryTagNames.add(tag);
}
}
nonCategoryTagNames.sort(compareByDisplayName);
return nonCategoryTagNames;
}
/**
* Get all the TagNames that are categories
*
* @return All the TagNames that are categories, in alphabetical order by
* displayName.
* @return All the TagNames that are categories.
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public List<TagName> getCategoryTagNames() throws TskCoreException {
return autopsyTagsManager.getAllTagNames().stream()
.filter(CategoryManager::isCategoryTagName)
.distinct().sorted()
.collect(Collectors.toList());
return controller.getCategoryManager().getCategorySet().getTagNames();
}
/**
@ -195,10 +210,6 @@ public final class DrawableTagsManager {
}
}
public TagName getTagName(DhsImageCategory cat) throws TskCoreException {
return getTagName(cat.getDisplayName());
}
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
}

View File

@ -55,7 +55,7 @@ public class VideoFile extends DrawableFile {
}
/**
* Get the genereric video thumbnail.
* Get the generic video thumbnail.
*
* @return The thumbnail.
*/

View File

@ -59,6 +59,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
public String getValueDisplayName() {
return Objects.equals(attr, DrawableAttribute.TAGS)
|| Objects.equals(attr, DrawableAttribute.CATEGORY)
? ((TagName) getValue()).getDisplayName()
: Objects.toString(getValue(), "unknown");
}
@ -74,8 +75,9 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
hash = 79 * hash + Objects.hashCode(this.val);
hash = 79 * hash + Objects.hashCode(this.attr);
if (this.dataSource != null)
hash = 79 * hash + (int)this.dataSource.getId();
if (this.dataSource != null) {
hash = 79 * hash + (int) this.dataSource.getId();
}
return hash;
}

View File

@ -62,7 +62,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
@ -70,9 +69,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@ -106,23 +103,23 @@ public class GroupManager {
private final ImageGalleryController controller;
/**
* Keeps track of the current path group
* - a change in path indicates the current path group is analyzed
* Keeps track of the current path group - a change in path indicates the
* current path group is analyzed
*/
@GuardedBy("this") //NOPMD
private GroupKey<?> currentPathGroup = null;
/**
* list of all analyzed groups - i.e. groups that are ready to be shown to user.
* These are groups under the selected groupBy attribute.
* list of all analyzed groups - i.e. groups that are ready to be shown to
* user. These are groups under the selected groupBy attribute.
*/
@GuardedBy("this") //NOPMD
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
/**
* list of unseen groups
* These are groups under the selected groupBy attribute.
* list of unseen groups These are groups under the selected groupBy
* attribute.
*/
@GuardedBy("this") //NOPMD
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
@ -187,14 +184,14 @@ public class GroupManager {
synchronized public Set<GroupKey<?>> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException {
Set<GroupKey<?>> resultSet = new HashSet<>();
for (DrawableAttribute<?> attr: DrawableAttribute.getGroupableAttrs()) {
for (DrawableAttribute<?> attr : DrawableAttribute.getGroupableAttrs()) {
for (Comparable<?> val : attr.getValue(file)) {
if (attr == DrawableAttribute.PATH) {
resultSet.add(new GroupKey(attr, val, file.getDataSource()));
} else if (attr == DrawableAttribute.TAGS) {
//don't show groups for the categories when grouped by tags.
if (CategoryManager.isNotCategoryTagName((TagName) val)) {
if (controller.getCategoryManager().isNotCategoryTagName((TagName) val)) {
resultSet.add(new GroupKey(attr, val, null));
}
} else {
@ -205,7 +202,6 @@ public class GroupManager {
return resultSet;
}
/**
*
* Returns GroupKeys for all the Groups the given file is a part of.
@ -324,8 +320,8 @@ public class GroupManager {
synchronized private void updateUnSeenGroups(DrawableGroup group) {
if (group.isSeen()) {
unSeenGroups.removeAll(group);
} else if (unSeenGroups.contains(group) == false &&
getGroupBy() == group.getGroupKey().getAttribute()) {
} else if (unSeenGroups.contains(group) == false
&& getGroupBy() == group.getGroupKey().getAttribute()) {
unSeenGroups.add(group);
}
sortUnseenGroups();
@ -390,7 +386,7 @@ public class GroupManager {
switch (groupKey.getAttribute().attrName) {
//these cases get special treatment
case CATEGORY:
return getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
return getFileIDsWithCategory((TagName) groupKey.getValue());
case TAGS:
return getFileIDsWithTag((TagName) groupKey.getValue());
case MIME_TYPE:
@ -405,33 +401,18 @@ public class GroupManager {
// @@@ This was kind of slow in the profiler. Maybe we should cache it.
// Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
synchronized public Set<Long> getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException {
synchronized public Set<Long> getFileIDsWithCategory(TagName category) throws TskCoreException {
Set<Long> fileIDsToReturn = Collections.emptySet();
try {
final DrawableTagsManager tagsManager = controller.getTagsManager();
if (category == DhsImageCategory.ZERO) {
Set<Long> fileIDs = new HashSet<>();
for (TagName catTagName : tagsManager.getCategoryTagNames()) {
if (notEqual(catTagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) {
tagsManager.getContentTagsByTagName(catTagName).stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
.map(ct -> ct.getContent().getId())
.filter(getDrawableDB()::isInDB)
.forEach(fileIDs::add);
}
}
fileIDsToReturn = getDrawableDB().findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS
} else {
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category));
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(category);
fileIDsToReturn = contentTags.stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
.filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
.map(ct -> ct.getContent().getId())
.collect(Collectors.toSet());
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS
throw ex;
@ -552,14 +533,14 @@ public class GroupManager {
synchronized public void handleTagAdded(ContentTagAddedEvent evt) {
GroupKey<?> newGroupKey = null;
final long fileID = evt.getAddedTag().getContent().getId();
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) {
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource());
if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(evt.getAddedTag().getName())) {
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, evt.getAddedTag().getName(), getDataSource());
for (GroupKey<?> oldGroupKey : groupMap.keySet()) {
if (oldGroupKey.equals(newGroupKey) == false) {
removeFromGroup(oldGroupKey, fileID);
}
}
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) {
} else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(evt.getAddedTag().getName())) {
newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource());
}
if (newGroupKey != null) {
@ -569,7 +550,8 @@ public class GroupManager {
}
/**
* Adds an analyzed file to the in-memory group data structures. Marks the group as unseen.
* Adds an analyzed file to the in-memory group data structures. Marks the
* group as unseen.
*
* @param group Group being added to (will be null if a group has not yet
* been created)
@ -584,10 +566,14 @@ public class GroupManager {
//if there wasn't already a DrawableGroup, then check if this group is now
// in an appropriate state to get one made.
// Path group, for example, only gets a DrawableGroup created when all files are analyzed
/* NOTE: With the current (Jan 2019) behavior of how we detect a PATH group as being analyzed, the group
* is not marked as analyzed until we add a file for another folder. So, when the last picture in a folder
* is added to the group, the call to 'populateIfAnalyzed' will still not return a group and therefore this
* method will never mark the group as unseen. */
/*
* NOTE: With the current (Jan 2019) behavior of how we detect a
* PATH group as being analyzed, the group is not marked as analyzed
* until we add a file for another folder. So, when the last picture
* in a folder is added to the group, the call to
* 'populateIfAnalyzed' will still not return a group and therefore
* this method will never mark the group as unseen.
*/
group = popuplateIfAnalyzed(groupKey, null);
} else {
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
@ -605,18 +591,14 @@ public class GroupManager {
GroupKey<?> groupKey = null;
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
final TagName deletedTagName = deletedTagInfo.getName();
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(deletedTagName), null);
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(deletedTagName)) {
if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, deletedTagName, null);
} else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null);
}
if (groupKey != null) {
final long fileID = deletedTagInfo.getContentID();
DrawableGroup g = removeFromGroup(groupKey, fileID);
if (controller.getCategoryManager().getTagName(DhsImageCategory.ZERO).equals(deletedTagName) == false) {
addFileToGroup(null, new GroupKey<>(DrawableAttribute.CATEGORY, DhsImageCategory.ZERO, null), fileID);
}
}
}
@ -653,7 +635,7 @@ public class GroupManager {
try {
DrawableFile file = getDrawableDB().getFileFromID(fileId);
String pathVal = file.getDrawablePath();
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource());
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH, pathVal, file.getDataSource());
updateCurrentPathGroup(pathGroupKey);
} catch (TskCoreException | TskDataException ex) {
@ -674,17 +656,18 @@ public class GroupManager {
}
/**
* Checks if the given path is different from the current path group.
* If so, updates the current path group as analyzed, and sets current path
* group to the given path.
* Checks if the given path is different from the current path group. If so,
* updates the current path group as analyzed, and sets current path group
* to the given path.
*
* The idea is that when the path of the files being processed changes,
* we have moved from one folder to the next, and the group for the
* previous PATH can be considered as analyzed and can be displayed.
* The idea is that when the path of the files being processed changes, we
* have moved from one folder to the next, and the group for the previous
* PATH can be considered as analyzed and can be displayed.
*
* NOTE: this a close approximation for when all files in a folder have been processed,
* but there's some room for error - files may go down the ingest pipleline
* out of order or the events may not always arrive in the same order
* NOTE: this a close approximation for when all files in a folder have been
* processed, but there's some room for error - files may go down the ingest
* pipleline out of order or the events may not always arrive in the same
* order
*
* @param groupKey
*/
@ -694,8 +677,7 @@ public class GroupManager {
if (this.currentPathGroup == null) {
currentPathGroup = groupKey;
}
else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
} else if (groupKey.getValue().toString().equalsIgnoreCase(this.currentPathGroup.getValue().toString()) == false) {
// mark the last path group as analyzed
getDrawableDB().markGroupAnalyzed(currentPathGroup);
popuplateIfAnalyzed(currentPathGroup, null);
@ -703,14 +685,14 @@ public class GroupManager {
currentPathGroup = groupKey;
}
}
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error setting is_analyzed status for group: %s", groupKey.getValue().toString()), ex); //NON-NLS
}
}
/**
* Resets current path group, after marking the current path group as analyzed.
* Resets current path group, after marking the current path group as
* analyzed.
*/
synchronized public void resetCurrentPathGroup() {
try {
@ -719,11 +701,11 @@ public class GroupManager {
popuplateIfAnalyzed(currentPathGroup, null);
currentPathGroup = null;
}
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error resetting last path group: %s", currentPathGroup.getValue().toString()), ex); //NON-NLS
}
}
/**
* If the group is analyzed (or other criteria based on grouping) and should
* be shown to the user, then add it to the appropriate data structures so
@ -770,8 +752,8 @@ public class GroupManager {
}
// Add to analyzedGroups only if it's the a group with the selected groupBy attribute
if ((analyzedGroups.contains(group) == false) &&
(getGroupBy() == group.getGroupKey().getAttribute())) {
if ((analyzedGroups.contains(group) == false)
&& (getGroupBy() == group.getGroupKey().getAttribute())) {
analyzedGroups.add(group);
sortAnalyzedGroups();
}
@ -944,11 +926,11 @@ public class GroupManager {
switch (groupBy.attrName) {
//these cases get special treatment
case CATEGORY:
results.putAll(null, Arrays.asList(DhsImageCategory.values()));
results.putAll(null, controller.getCategoryManager().getCategories());
break;
case TAGS:
results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream()
.filter(CategoryManager::isNotCategoryTagName)
.filter(controller.getCategoryManager()::isNotCategoryTagName)
.collect(Collectors.toList()));
break;

View File

@ -63,9 +63,7 @@ public class GroupSortBy implements Comparator<DrawableGroup> {
*/
public final static GroupSortBy PRIORITY
= new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
Comparator.comparing(DrawableGroup::getHashHitDensity)
.thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
.reversed());
Comparator.comparing(DrawableGroup::getHashHitDensity).reversed());
private final static ObservableList<GroupSortBy> values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));

View File

@ -56,7 +56,7 @@ public final class GuiUtils {
/**
* Create a MenuItem that performs the given action and also set the Action
* as the action for the given Button. Usefull to have a SplitMenuButton
* as the action for the given Button. Useful to have a SplitMenuButton
* remember the last chosen menu item as its action.
*
* @param button

View File

@ -34,10 +34,10 @@ import javafx.scene.layout.VBox;
import javafx.util.Pair;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent;
import org.sleuthkit.datamodel.TagName;
/**
* Displays summary statistics (counts) for each group
@ -45,13 +45,13 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChan
public class SummaryTablePane extends AnchorPane {
@FXML
private TableColumn<Pair<DhsImageCategory, Long>, String> catColumn;
private TableColumn<Pair<TagName, Long>, String> catColumn;
@FXML
private TableColumn<Pair<DhsImageCategory, Long>, Long> countColumn;
private TableColumn<Pair<TagName, Long>, Long> countColumn;
@FXML
private TableView<Pair<DhsImageCategory, Long>> tableView;
private TableView<Pair<TagName, Long>> tableView;
private final ImageGalleryController controller;
@ -97,9 +97,9 @@ public class SummaryTablePane extends AnchorPane {
*/
@Subscribe
public void handleCategoryChanged(CategoryChangeEvent evt) {
final ObservableList<Pair<DhsImageCategory, Long>> data = FXCollections.observableArrayList();
final ObservableList<Pair<TagName, Long>> data = FXCollections.observableArrayList();
if (Case.isCaseOpen()) {
for (DhsImageCategory cat : DhsImageCategory.values()) {
for (TagName cat : controller.getCategoryManager().getCategories()) {
data.add(new Pair<>(cat, controller.getCategoryManager().getCategoryCount(cat)));
}
}

View File

@ -20,12 +20,9 @@ package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -64,7 +61,6 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_DELETED;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
@ -220,13 +216,13 @@ public class Toolbar extends ToolBar {
});
initTagMenuButton();
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller);
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(controller.getCategoryManager().getCategories().get(0), controller);
catGroupMenuButton.setOnAction(cat5GroupAction);
catGroupMenuButton.setText(cat5GroupAction.getText());
catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic());
catGroupMenuButton.showingProperty().addListener(showing -> {
if (catGroupMenuButton.isShowing()) {
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()),
List<MenuItem> categoryMenues = Lists.transform(controller.getCategoryManager().getCategories(),
cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller)));
catGroupMenuButton.getItems().setAll(categoryMenues);
}

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import javafx.application.Platform;
@ -34,10 +36,11 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TagName.HTML_COLOR;
/**
* Interface for classes that are views of a single DrawableFile. Implementation
@ -54,19 +57,9 @@ public interface DrawableView {
static final CornerRadii CAT_CORNER_RADII = new CornerRadii(3);
static final Border HASH_BORDER = new Border(new BorderStroke(Color.PURPLE, BorderStrokeStyle.DASHED, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
Border HASH_BORDER = new Border(new BorderStroke(Color.CYAN, BorderStrokeStyle.DASHED, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT1_BORDER = new Border(new BorderStroke(DhsImageCategory.ONE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT2_BORDER = new Border(new BorderStroke(DhsImageCategory.TWO.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT3_BORDER = new Border(new BorderStroke(DhsImageCategory.THREE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT4_BORDER = new Border(new BorderStroke(DhsImageCategory.FOUR.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT5_BORDER = new Border(new BorderStroke(DhsImageCategory.FIVE.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
static final Border CAT0_BORDER = new Border(new BorderStroke(DhsImageCategory.ZERO.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
Map<String, Border> BORDER_MAP = new HashMap<>();
Region getCategoryBorderRegion();
@ -115,38 +108,38 @@ public interface DrawableView {
}
static Border getCategoryBorder(DhsImageCategory category) {
if (category != null) {
switch (category) {
case ONE:
return CAT1_BORDER;
case TWO:
return CAT2_BORDER;
case THREE:
return CAT3_BORDER;
case FOUR:
return CAT4_BORDER;
case FIVE:
return CAT5_BORDER;
case ZERO:
default:
return CAT0_BORDER;
/**
* Get the boarder for the given category.
*
* Static instances of the boarders will lazily constructed and stored in
* the BORDER_MAP.
*
* @param category
*
* @return
*/
static Border getCategoryBorder(TagName category) {
Border border = null;
if (category != null && category.getColor() != HTML_COLOR.NONE) {
border = BORDER_MAP.get(category.getDisplayName());
if (border == null) {
border = new Border(new BorderStroke(Color.web(category.getColor().getRgbValue()), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
BORDER_MAP.put(category.getDisplayName(), border);
}
} else {
return CAT0_BORDER;
}
return border;
}
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
default DhsImageCategory updateCategory() {
default TagName updateCategory() {
if (getFile().isPresent()) {
final DhsImageCategory category = getFile().map(DrawableFile::getCategory).orElse(DhsImageCategory.ZERO);
final Border border = hasHashHit() && (category == DhsImageCategory.ZERO) ? HASH_BORDER : getCategoryBorder(category);
final TagName tagNameCat = getFile().map(DrawableFile::getCategory).orElse(null);
final Border border = hasHashHit() ? HASH_BORDER : getCategoryBorder(tagNameCat);
Platform.runLater(() -> getCategoryBorderRegion().setBorder(border));
return category;
return tagNameCat;
} else {
return DhsImageCategory.ZERO;
return null;
}
}
}

View File

@ -182,35 +182,6 @@
</Button>
</children>
</HBox>
<HBox fx:id="catSegmentedContainer" alignment="CENTER" maxWidth="-Infinity" spacing="5.0">
<children>
<Label fx:id="catHeadingLabel" text="Category:">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/category-icon.png" />
</image>
</ImageView>
</graphic>
</Label>
<SegmentedButton fx:id="catSegmentedButton">
<buttons>
<RadioButton fx:id="cat0Toggle" mnemonicParsing="false" text="0" HBox.hgrow="ALWAYS">
<toggleGroup>
<ToggleGroup fx:id="cat" />
</toggleGroup></RadioButton>
<RadioButton fx:id="cat1Toggle" mnemonicParsing="false" style="" styleClass="button" text="1" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
<RadioButton id="Cat2Toggle" fx:id="cat2Toggle" mnemonicParsing="false" styleClass="button" text="2" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
<RadioButton fx:id="cat3Toggle" mnemonicParsing="false" styleClass="button" text="3" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
<RadioButton fx:id="cat4Toggle" mnemonicParsing="false" styleClass="button" text="4" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
<RadioButton fx:id="cat5Toggle" mnemonicParsing="false" text="5" toggleGroup="$cat" HBox.hgrow="ALWAYS" />
</buttons>
</SegmentedButton>
</children>
<padding>
<Insets left="5.0" />
</padding>
</HBox>
</items>
</ToolBar>
</children>

View File

@ -19,12 +19,10 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -50,7 +48,6 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
@ -86,10 +83,7 @@ import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox;
@ -111,7 +105,6 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
@ -134,6 +127,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -176,19 +170,6 @@ public class GroupPane extends BorderPane {
private SplitMenuButton tagSelectedSplitMenu;
@FXML
private ToolBar headerToolBar;
@FXML
private ToggleButton cat0Toggle;
@FXML
private ToggleButton cat1Toggle;
@FXML
private ToggleButton cat2Toggle;
@FXML
private ToggleButton cat3Toggle;
@FXML
private ToggleButton cat4Toggle;
@FXML
private ToggleButton cat5Toggle;
@FXML
private SegmentedButton segButton;
@ -220,11 +201,6 @@ public class GroupPane extends BorderPane {
@FXML
private Label catContainerLabel;
@FXML
private Label catHeadingLabel;
@FXML
private HBox catSegmentedContainer;
@FXML
private HBox catSplitMenuContainer;
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
@ -244,12 +220,18 @@ public class GroupPane extends BorderPane {
private ContextMenu contextMenu;
/** the current GroupViewMode of this GroupPane */
/**
* the current GroupViewMode of this GroupPane
*/
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
/** the grouping this pane is currently the view for */
/**
* the grouping this pane is currently the view for
*/
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
private final Map<String, ToggleButton> toggleButtonMap = new HashMap<>();
/**
* Map from fileIDs to their assigned cells in the tile view. This is used
* to determine whether fileIDs are visible or are offscreen. No entry
@ -307,7 +289,35 @@ public class GroupPane extends BorderPane {
}
void syncCatToggle(DrawableFile file) {
getToggleForCategory(file.getCategory()).setSelected(true);
TagName tagName = file.getCategory();
if (tagName != null) {
getToggleForCategory(tagName).setSelected(true);
}
}
/**
* Returns a toggle button for the given TagName.
*
* @param tagName TagName to create a button for.
*
* @return A new instance of a ToggleButton.
*/
private ToggleButton getToggleForCategory(TagName tagName) {
ToggleButton button = toggleButtonMap.get(tagName.getDisplayName());
if (button == null) {
String[] split = tagName.getDisplayName().split(":");
split = split[0].split("-");
int category = Integer.parseInt(split[1]);
button = new ToggleButton();
button.setText(Integer.toString(category));
toggleButtonMap.put(tagName.getDisplayName(), button);
}
return button;
}
public void activateTileViewer() {
@ -353,25 +363,6 @@ public class GroupPane extends BorderPane {
return grouping.getReadOnlyProperty();
}
private ToggleButton getToggleForCategory(DhsImageCategory category) {
switch (category) {
case ZERO:
return cat0Toggle;
case ONE:
return cat1Toggle;
case TWO:
return cat2Toggle;
case THREE:
return cat3Toggle;
case FOUR:
return cat4Toggle;
case FIVE:
return cat5Toggle;
default:
throw new UnsupportedOperationException("Unknown category: " + category.name());
}
}
/**
* called automatically during constructor by FXMLConstructor.
*
@ -384,12 +375,6 @@ public class GroupPane extends BorderPane {
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
"GroupPane.catHeadingLabel.displayText=Category:"})
void initialize() {
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
@ -399,21 +384,6 @@ public class GroupPane extends BorderPane {
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
assert seenByOtherExaminersCheckBox != null : "fx:id=\"seenByOtherExaminersCheckBox\" was not injected: check your FXML file 'GroupPane.fxml'.";
for (DhsImageCategory cat : DhsImageCategory.values()) {
ToggleButton toggleForCategory = getToggleForCategory(cat);
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
toggleForCategory.getStyleClass().remove("radio-button");
toggleForCategory.getStyleClass().add("toggle-button");
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
if (toggleSelected && slideShowPane != null) {
slideShowPane.getFileID().ifPresent(fileID -> {
selectionModel.clearAndSelect(fileID);
new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
});
}
});
}
//configure flashing glow animation on next unseen group button
flashAnimation.setCycleCount(Timeline.INDEFINITE);
flashAnimation.setAutoReverse(true);
@ -447,14 +417,14 @@ public class GroupPane extends BorderPane {
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS
);
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(controller.getCategoryManager().getCategories().get(0), controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
List<MenuItem> categoryMenues = transform(controller.getCategoryManager().getCategories(),
cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
catSelectedSplitMenu.getItems().setAll(categoryMenues);
@ -466,16 +436,21 @@ public class GroupPane extends BorderPane {
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
catHeadingLabel.setText(Bundle.GroupPane_catHeadingLabel_displayText());
//show categorization controls depending on group view mode
headerToolBar.getItems().remove(catSegmentedContainer);
// This seems to be the only way to make sure the when the user switches
// to SLIDE_SHOW the first time that the undo\redo buttons are removed.
headerToolBar.getItems().remove(undoButton);
headerToolBar.getItems().remove(redoButton);
headerToolBar.getItems().add(undoButton);
headerToolBar.getItems().add(redoButton);
groupViewMode.addListener((ObservableValue<? extends GroupViewMode> observable, GroupViewMode oldValue, GroupViewMode newValue) -> {
if (newValue == GroupViewMode.SLIDE_SHOW) {
headerToolBar.getItems().remove(catSplitMenuContainer);
headerToolBar.getItems().add(catSegmentedContainer);
headerToolBar.getItems().remove(undoButton);
headerToolBar.getItems().remove(redoButton);
} else {
headerToolBar.getItems().remove(catSegmentedContainer);
headerToolBar.getItems().add(catSplitMenuContainer);
headerToolBar.getItems().add(undoButton);
headerToolBar.getItems().add(redoButton);
}
});
@ -775,41 +750,8 @@ public class GroupPane extends BorderPane {
selectAllFiles();
t.consume();
}
ObservableSet<Long> selected = selectionModel.getSelected();
if (selected.isEmpty() == false) {
DhsImageCategory cat = keyCodeToCat(t.getCode());
if (cat != null) {
new CategorizeAction(controller, cat, selected).handle(null);
}
}
}
}
private DhsImageCategory keyCodeToCat(KeyCode t) {
if (t != null) {
switch (t) {
case NUMPAD0:
case DIGIT0:
return DhsImageCategory.ZERO;
case NUMPAD1:
case DIGIT1:
return DhsImageCategory.ONE;
case NUMPAD2:
case DIGIT2:
return DhsImageCategory.TWO;
case NUMPAD3:
case DIGIT3:
return DhsImageCategory.THREE;
case NUMPAD4:
case DIGIT4:
return DhsImageCategory.FOUR;
case NUMPAD5:
case DIGIT5:
return DhsImageCategory.FIVE;
}
}
return null;
}
private void handleArrows(KeyEvent t) {
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();

View File

@ -19,9 +19,9 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static java.util.Collections.singletonMap;
import java.util.List;
import java.util.Objects;
@ -56,7 +56,6 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
@ -165,20 +164,50 @@ public class MetaDataPane extends DrawableUIBase {
titledPane.setText(Bundle.MetaDataPane_titledPane_displayName());
}
/**
* Returns the display string for the given pair.
*
* @param p A DrawableAttribute and its collection.
*
* @return The string to display.
*/
@SuppressWarnings("unchecked")
static private String getValueDisplayString(Pair<DrawableAttribute<?>, Collection<?>> p) {
if (p.getKey() == DrawableAttribute.TAGS) {
return ((Collection<TagName>) p.getValue()).stream()
.map(TagName::getDisplayName)
.filter(DhsImageCategory::isNotCategoryName)
.collect(Collectors.joining(" ; "));
private String getValueDisplayString(Pair<DrawableAttribute<?>, Collection<?>> p) {
if (p.getKey() == DrawableAttribute.TAGS || p.getKey() == DrawableAttribute.CATEGORY) {
return getTagDisplayNames((Collection<TagName>) p.getValue(), p.getKey());
} else {
return p.getValue().stream()
.map(value -> Objects.toString(value, ""))
.collect(Collectors.joining(" ; "));
}
}
/**
* Create the list of TagName displayNames for either Tags or Categories.
*
* @param tagNameList List of TagName values
* @param attribute A DrawableAttribute value either CATEGORY or TAGS
*
* @return A list of TagNames separated by ; or an empty string.
*/
private String getTagDisplayNames(Collection<TagName> tagNameList, DrawableAttribute<?> attribute) {
String displayStr = "";
CategoryManager controller = getController().getCategoryManager();
List<String> nameList = new ArrayList<>();
if (tagNameList != null && !tagNameList.isEmpty()) {
for (TagName tagName : tagNameList) {
if ((attribute == DrawableAttribute.CATEGORY && controller.isCategoryTagName(tagName))
|| (attribute == DrawableAttribute.TAGS && !controller.isCategoryTagName(tagName))) {
nameList.add(tagName.getDisplayName());
}
}
displayStr = String.join(";", nameList);
}
return displayStr;
}
@Override
synchronized protected void setFileHelper(Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID));

View File

@ -50,12 +50,12 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
import org.sleuthkit.datamodel.TagName;
/**
* Displays the files of a group one at a time. Designed to be embedded in a
@ -297,14 +297,14 @@ public class SlideShowView extends DrawableTileBase {
@Override
@ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() {
public TagName updateCategory() {
Optional<DrawableFile> file = getFile();
if (file.isPresent()) {
DhsImageCategory updateCategory = super.updateCategory();
TagName updateCategory = super.updateCategory();
Platform.runLater(() -> getGroupPane().syncCatToggle(file.get()));
return updateCategory;
} else {
return DhsImageCategory.ZERO;
return null;
}
}

View File

@ -117,6 +117,7 @@ class GroupCellFactory {
final Node graphic = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
: group.getGroupKey().getGraphic();
final String text = getCellText(cell);
final String style = getSeenStyleClass(cell);
@ -157,8 +158,8 @@ class GroupCellFactory {
*/
private String getCountsText(GroupCell<?> cell) {
return cell.getGroup()
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
.map(group
-> " (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null

View File

@ -114,12 +114,8 @@ final class ExtractSru extends Extract {
AbstractFile sruAbstractFile = getSruFile(dataSource, tempDirPath);
String sruFileName = tempDirPath + File.separator + sruAbstractFile.getId() + "_" + sruAbstractFile.getName();
if (sruFileName == null) {
this.addErrorMessage(Bundle.ExtractSru_process_errormsg_find_srudb_dat());
logger.log(Level.SEVERE, "SRUDB.dat file not found"); //NON-NLS
return; //If we cannot find the srudb.dat file we cannot proceed
if (sruAbstractFile == null) {
return; //If we cannot find the srudb.dat file we cannot proceed which is ok
}
final String sruDumper = getPathForSruDumper();
@ -135,6 +131,7 @@ final class ExtractSru extends Extract {
try {
String modOutFile = modOutPath + File.separator + sruAbstractFile.getId() + "_srudb.db3";
String sruFileName = tempDirPath + File.separator + sruAbstractFile.getId() + "_" + sruAbstractFile.getName();
extractSruFiles(sruDumper, sruFileName, modOutFile, tempDirPath, softwareHiveFileName);