diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 6c6e102dac..47fff7320f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -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 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 removedTagList) { + eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag, removedTagList)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java index 45068164c1..e9793a51b0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/BlackBoardArtifactTagAddedEvent.java @@ -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 implements Serializable { +public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent implements Serializable { private static final long serialVersionUID = 1L; @@ -38,8 +40,8 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent removedTagList) { + super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag, (removedTagList != null ? getDeletedInfo(removedTagList) : null)); } /** @@ -54,4 +56,24 @@ public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent getDeletedInfo(List deletedTagList) { + List deletedInfoList = new ArrayList<>(); + if (deletedTagList != null) { + for (BlackboardArtifactTag tag : deletedTagList) { + deletedInfoList.add(new DeletedBlackboardArtifactTagInfo(tag)); + } + } + + return deletedInfoList; + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java index b3e55d3eac..10a80c772e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/ContentTagAddedEvent.java @@ -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 implements Serializable { +public class ContentTagAddedEvent extends TagAddedEvent implements Serializable { private static final long serialVersionUID = 1L; @@ -38,8 +40,8 @@ public class ContentTagAddedEvent extends TagAddedEvent 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 deletedTagList) { + super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag, getDeletedInfo(deletedTagList)); } /** @@ -50,7 +52,26 @@ public class ContentTagAddedEvent extends TagAddedEvent 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 getDeletedInfo(List deletedTagList) { + List deletedInfoList = new ArrayList<>(); + if (deletedTagList != null) { + for (ContentTag tag : deletedTagList) { + deletedInfoList.add(new DeletedContentTagInfo(tag)); + } + } + + return deletedInfoList; + } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java index ab737d7004..25a35a6d6c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java @@ -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 extends AutopsyEvent implements Serializable { +abstract class TagAddedEvent> extends AutopsyEvent implements Serializable { private static final long serialVersionUID = 1L; @@ -39,6 +41,8 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri * re-loaded from the database in getNewValue() */ private transient T tag; + + private List deletedTagInfoList; /** * The id of the tag that was added. This will be used to re-load the @@ -50,10 +54,19 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri this(propertyName, addedTag, null); } - TagAddedEvent(String propertyName, T addedTag, DeletedTagInfo 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 deletedTagInfoList) { + super(propertyName, deletedTagInfoList, null); tag = addedTag; tagID = addedTag.getId(); + this.deletedTagInfoList = deletedTagInfoList; } /** @@ -73,7 +86,7 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri public T getAddedTag() { return getNewValue(); } - + @Override public T getNewValue() { /** @@ -95,6 +108,21 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri return null; } } + + /** + * 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 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 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java index 2e70d407a0..0c65f3c58d 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagNameDefinition.java @@ -80,7 +80,7 @@ final class TagNameDefinition implements Comparable { 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)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 5bdb2dc64e..49388d619e 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -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); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index 843e476499..400eeeb033 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -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 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( diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java index e923d6dbd6..2c33d054cb 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java @@ -257,6 +257,6 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter return waypointMostRecent; } - return null; + return -1L; } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java index 146bc156ed..f80eef206b 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/EventsModel.java @@ -657,6 +657,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 @@ -782,7 +793,6 @@ public final class EventsModel { * @throws TskCoreException */ public synchronized void invalidateCaches(Collection updatedEventIDs) throws TskCoreException { - populateDataSourcesCache(); minEventTimeCache.invalidateAll(); maxEventTimeCache.invalidateAll(); idsToEventsCache.invalidateAll(emptyIfNull(updatedEventIDs)); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index cf8e9c46f7..6b41d849d8 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -783,7 +783,7 @@ public class TimeLineController { break; case DATA_SOURCE_ADDED: future = executor.submit(() -> { - filteredEvents.invalidateCaches(null); + filteredEvents.handleDataSourceAdded(); return null; }); break; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 9d8c8b1269..5ab3b8c84b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -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; @@ -107,6 +108,8 @@ public final class ImageGalleryController { Case.Events.CONTENT_TAG_DELETED, 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 @@ -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); @@ -720,6 +725,28 @@ public final class ImageGalleryController { private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException { 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 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. diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java index 6ea1e91769..125f5489ca 100755 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/UpdateDrawableFileTask.java @@ -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 { @@ -60,5 +60,5 @@ class UpdateDrawableFileTask extends DrawableDbTask { Logger.getLogger(UpdateDrawableFileTask.class.getName()).log(Level.SEVERE, "Error in update file task", ex); //NON-NLS } } - + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index d847a21a58..13703f417b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -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 selectedFileIDs; private final Boolean createUndo; + private final TagName tagName; - public CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set selectedFileIDs) { - this(controller, cat, selectedFileIDs, true); + public CategorizeAction(ImageGalleryController controller, TagName tagName, Set selectedFileIDs) { + this(controller, tagName, selectedFileIDs, true); } - private CategorizeAction(ImageGalleryController controller, DhsImageCategory cat, Set selectedFileIDs, Boolean createUndo) { - super(cat.getDisplayName()); + private CategorizeAction(ImageGalleryController controller, TagName tagName, Set 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 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 fileIDs; final boolean createUndo; - final DhsImageCategory cat; + final TagName catTagName; - CategorizeDrawableFileTask(Set fileIDs, @Nonnull DhsImageCategory cat, boolean createUndo) { + CategorizeDrawableFileTask(Set 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 oldCats = new HashMap<>(); - TagName tagName = categoryManager.getTagName(cat); + Map 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 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, ""); - } + + 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 oldCategories; + private final TagName newTagNameCategory; + private final ImmutableMap oldTagNameCategories; private final ImageGalleryController controller; - CategorizationChange(ImageGalleryController controller, DhsImageCategory newCategory, Map oldCategories) { + CategorizationChange(ImageGalleryController controller, TagName newTagNameCategory, Map 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 entry : oldCategories.entrySet()) { + for (Map.Entry 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)); + } + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index b49e1ca30c..20e8cb2081 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -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 catCountMap = new HashMap<>(); + final Map 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 catCountMap, DhsImageCategory newCat, ObservableList fileIDs) { + private void showConfirmationDialog(final Map catCountMap, TagName newCat, ObservableList fileIDs) { ButtonType categorizeButtonType = new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY); VBox textFlow = new VBox(); - for (Map.Entry entry : catCountMap.entrySet()) { - if (entry.getValue() > 0 - && notEqual(entry.getKey(), newCat)) { + for (Map.Entry 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; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java index bb8cd9de96..742ac300c1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeSelectedFilesAction.java @@ -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()) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index 2fb97b6e95..5eaf8b4ad7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -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 categoryCounts + private final LoadingCache 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 catTagNameMap - = CacheBuilder.newBuilder().build(new CacheLoader() { - @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 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 fileIDs, DhsImageCategory newCategory) { - categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory)); + public void fireChange(Collection 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; - } - } - } - } 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); + List removedTags = event.getDeletedTags(); + if (removedTags != null) { + for (DeletedContentTagInfo tagInfo : removedTags) { + handleDeletedInfo(tagInfo); + } + } + + 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 fileIDs; - private final DhsImageCategory newCategory; + private final TagName tagName; - public CategoryChangeEvent(Collection fileIDs, DhsImageCategory newCategory) { + public CategoryChangeEvent(Collection 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 getSortedTagNames(List tagNames) { + Comparator compareByDisplayName = new Comparator() { + @Override + public int compare(TagName tagName1, TagName tagName2) { + return tagName1.getDisplayName().compareTo(tagName2.getDisplayName()); + } + }; + + List sortedTagNames = new ArrayList<>(tagNames); + sortedTagNames.sort(compareByDisplayName); + + return sortedTagNames; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java index 25b50811b0..03ea2e3292 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableAttribute.java @@ -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> { * //TODO: this has lead to awkward hard to maintain code, and little * advantage. move categories into DrawableDB? */ - public final static DrawableAttribute CATEGORY - = new DrawableAttribute(AttributeName.CATEGORY, Bundle.DrawableAttribute_category(), + public final static DrawableAttribute CATEGORY + = new DrawableAttribute(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> { .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(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index f634e8387d..3aa39bd2fd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -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,14 +477,14 @@ 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) { throw new TskCoreException("Failed to create/open the drawables database", ex); //NON-NLS } } - + /** * Checks if the specified table exists in Drawable DB * @@ -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(",", "(", ")")); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 9c84f6d3b2..112c7ab6ec 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -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 category = new SimpleObjectProperty<>(null); + private final SimpleObjectProperty 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 categoryProperty() { - return category; + public SimpleObjectProperty 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 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) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index d2cdc484ce..ec06c6e343 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -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 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() { + @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 getNonCategoryTagNames() throws TskCoreException { - return autopsyTagsManager.getAllTagNames().stream() - .filter(CategoryManager::isNotCategoryTagName) - .distinct().sorted() - .collect(Collectors.toList()); + List nonCategoryTagNames = new ArrayList<>(); + List 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 getCategoryTagNames() throws TskCoreException { - return autopsyTagsManager.getAllTagNames().stream() - .filter(CategoryManager::isCategoryTagName) - .distinct().sorted() - .collect(Collectors.toList()); + return controller.getCategoryManager().getCategorySet().getTagNames(); } /** @@ -190,15 +205,11 @@ public final class DrawableTagsManager { returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); if (returnTagName != null) { return returnTagName; - } + } throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); } } - 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); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java index 5f6f72279e..f972e6cc9e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/VideoFile.java @@ -55,7 +55,7 @@ public class VideoFile extends DrawableFile { } /** - * Get the genereric video thumbnail. + * Get the generic video thumbnail. * * @return The thumbnail. */ diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java index 52dadf5ecd..e750c5ff2a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java @@ -59,6 +59,7 @@ public class GroupKey> implements Comparable 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> implements Comparable 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; } @@ -99,20 +101,20 @@ public class GroupKey> implements Comparable if (!Objects.equals(this.attr, other.attr)) { return false; } - + // Data source is significant only for PATH based groups. if (this.attr == DrawableAttribute.PATH) { if (this.dataSource != null && other.dataSource != null) { - return this.dataSource.getId() == other.dataSource.getId(); + return this.dataSource.getId() == other.dataSource.getId(); } else if (this.dataSource == null && other.dataSource == null) { // neither group has a datasource return true; } else { - // one group has a datasource, other doesn't + // one group has a datasource, other doesn't return false; } } - + return true; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 59dae2af00..774f6ea3e5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -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 analyzedGroups = FXCollections.observableArrayList(); private final ObservableList 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 unSeenGroups = FXCollections.observableArrayList(); @@ -186,15 +183,15 @@ public class GroupManager { @SuppressWarnings({"rawtypes", "unchecked"}) synchronized public Set> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException { Set> 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 { @@ -204,9 +201,8 @@ public class GroupManager { } return resultSet; } - - /** + /** * * Returns GroupKeys for all the Groups the given file is a part of. * @@ -225,7 +221,7 @@ public class GroupManager { } return Collections.emptySet(); } - + /** * @param groupKey * @@ -244,7 +240,7 @@ public class GroupManager { setGroupBy(DrawableAttribute.PATH); setSortOrder(SortOrder.ASCENDING); setDataSource(null); - + unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); unSeenGroups.clear(); analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); @@ -300,12 +296,12 @@ public class GroupManager { public ListenableFuture markGroupUnseen(DrawableGroup group) { return exec.submit(() -> { try { - + getDrawableDB().markGroupUnseen(group.getGroupKey()); // only update and reshuffle if its new results if (group.isSeen() == true) { group.setSeen(false); - } + } // The group may already be in 'unseen' state, e.g. when new files are added, // but not be on the unseenGroupsList yet. updateUnSeenGroups(group); @@ -314,7 +310,7 @@ public class GroupManager { } }); } - + /** * Update unseenGroups list accordingly based on the current status of * 'group'. Removes it if it is seen or adds it if it is unseen. @@ -322,13 +318,13 @@ public class GroupManager { * @param group */ synchronized private void updateUnSeenGroups(DrawableGroup group) { - if (group.isSeen()) { - unSeenGroups.removeAll(group); - } else if (unSeenGroups.contains(group) == false && - getGroupBy() == group.getGroupKey().getAttribute()) { - unSeenGroups.add(group); - } - sortUnseenGroups(); + if (group.isSeen()) { + unSeenGroups.removeAll(group); + } 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 getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { + synchronized public Set getFileIDsWithCategory(TagName category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); try { final DrawableTagsManager tagsManager = controller.getTagsManager(); - if (category == DhsImageCategory.ZERO) { - Set 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 contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(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()); - } + List 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,16 +566,20 @@ 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. group.addFile(fileID); } - + // reset the seen status for the group (if it is currently considered analyzed) if (group != null) { markGroupUnseen(group); @@ -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,13 +635,13 @@ 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) { logger.log(Level.WARNING, "Error getting drawabledb for fileId " + fileId, ex); - } - + } + // Update all the groups that this file belongs to Set> groupsForFile = getAllGroupKeysForFile(fileId); for (GroupKey gk : groupsForFile) { @@ -672,45 +654,45 @@ public class GroupManager { //we fire this event for all files so that the category counts get updated during initial db population controller.getCategoryManager().fireChange(updatedFileIDs, null); } - + /** - * 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. - * - * 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 + * 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. + * + * 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 */ synchronized private void updateCurrentPathGroup(GroupKey groupKey) { try { if (groupKey.getAttribute() == DrawableAttribute.PATH) { - + 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); - + 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 @@ -768,12 +750,12 @@ public class GroupManager { controller.getCategoryManager().registerListener(group); groupMap.put(groupKey, group); } - + // Add to analyzedGroups only if it's the a group with the selected groupBy attribute - if ((analyzedGroups.contains(group) == false) && - (getGroupBy() == group.getGroupKey().getAttribute())) { - analyzedGroups.add(group); - sortAnalyzedGroups(); + if ((analyzedGroups.contains(group) == false) + && (getGroupBy() == group.getGroupKey().getAttribute())) { + analyzedGroups.add(group); + sortAnalyzedGroups(); } updateUnSeenGroups(group); @@ -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; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java index 97a75f0f5b..b187f57297 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java @@ -63,9 +63,7 @@ public class GroupSortBy implements Comparator { */ 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 values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java index 8288ee4abf..f032698578 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java @@ -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 diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index 4884f580b7..f297d2b1af 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -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, String> catColumn; + private TableColumn, String> catColumn; @FXML - private TableColumn, Long> countColumn; + private TableColumn, Long> countColumn; @FXML - private TableView> tableView; + private TableView> tableView; private final ImageGalleryController controller; @@ -97,9 +97,9 @@ public class SummaryTablePane extends AnchorPane { */ @Subscribe public void handleCategoryChanged(CategoryChangeEvent evt) { - final ObservableList> data = FXCollections.observableArrayList(); + final ObservableList> 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))); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index ebe8ae3698..f492b5f325 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -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 categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), + List categoryMenues = Lists.transform(controller.getCategoryManager().getCategories(), cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); catGroupMenuButton.getItems().setAll(categoryMenues); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java index caca379014..472f0b59e9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java @@ -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 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; } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index 79134d0e4d..5dcd725381 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -182,35 +182,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 559fe4b714..fed02ac6e4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -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 = 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 grouping = new ReadOnlyObjectWrapper<>(); + private final Map 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 @@ -278,7 +260,7 @@ public class GroupPane extends BorderPane { undoAction = new UndoAction(controller); redoAction = new RedoAction(controller); - FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS + FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS } GroupViewMode getGroupViewMode() { @@ -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 categoryMenues = transform(asList(DhsImageCategory.values()), + List 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 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); } }); @@ -527,7 +502,7 @@ public class GroupPane extends BorderPane { //listen to tile selection and make sure it is visible in scroll area selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW - && slideShowPane != null) { + && slideShowPane != null) { slideShowPane.setFile(newFileId); } else { scrollToFileID(newFileId); @@ -775,42 +750,9 @@ public class GroupPane extends BorderPane { selectAllFiles(); t.consume(); } - ObservableSet 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(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index 7766317995..e83f970d65 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -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, Collection> p) { - if (p.getKey() == DrawableAttribute.TAGS) { - return ((Collection) p.getValue()).stream() - .map(TagName::getDisplayName) - .filter(DhsImageCategory::isNotCategoryName) - .collect(Collectors.joining(" ; ")); + private String getValueDisplayString(Pair, Collection> p) { + if (p.getKey() == DrawableAttribute.TAGS || p.getKey() == DrawableAttribute.CATEGORY) { + return getTagDisplayNames((Collection) 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 tagNameList, DrawableAttribute attribute) { + String displayStr = ""; + CategoryManager controller = getController().getCategoryManager(); + List 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)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java index e4d565d0ee..16796fb996 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/SlideShowView.java @@ -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 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; } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java index c5497d31af..bc0567d85a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java @@ -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,10 +158,10 @@ class GroupCellFactory { */ private String getCountsText(GroupCell cell) { return cell.getGroup() - .map(group -> - " (" + (sortOrder.get() == GroupComparators.ALPHABETICAL - ? group.getSize() - : sortOrder.get().getFormattedValueOfGroup(group)) + ")" + .map(group + -> " (" + (sortOrder.get() == GroupComparators.ALPHABETICAL + ? group.getSize() + : sortOrder.get().getFormattedValueOfGroup(group)) + ")" ).orElse(""); //if item is null or group is null } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java index 08782097bf..77dac6f22c 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractSru.java @@ -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);