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

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.casemodule.events; package org.sleuthkit.autopsy.casemodule.events;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.TagDeletedEvent.DeletedTagInfo; 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 * Base Class for events that are fired when a Tag is added
*/ */
abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Serializable { abstract class TagAddedEvent<T extends Tag, V extends DeletedTagInfo<T>> extends AutopsyEvent implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -39,6 +41,8 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
* re-loaded from the database in getNewValue() * re-loaded from the database in getNewValue()
*/ */
private transient T tag; private transient T tag;
private List<V> deletedTagInfoList;
/** /**
* The id of the tag that was added. This will be used to re-load the * The id of the tag that was added. This will be used to re-load the
@ -50,10 +54,19 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
this(propertyName, addedTag, null); this(propertyName, addedTag, null);
} }
TagAddedEvent(String propertyName, T addedTag, DeletedTagInfo<T> deletedTagInfo) { /**
super(propertyName, deletedTagInfo, null); * Construct a TagAddedEvent.
*
* @param propertyName Name of property changing
* @param addedTag Instance of added tag.
* @param deletedTagInfoList List of tags deleted as a result of the
* addition of addedTag.
*/
TagAddedEvent(String propertyName, T addedTag, List<V> deletedTagInfoList) {
super(propertyName, deletedTagInfoList, null);
tag = addedTag; tag = addedTag;
tagID = addedTag.getId(); tagID = addedTag.getId();
this.deletedTagInfoList = deletedTagInfoList;
} }
/** /**
@ -73,7 +86,7 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
public T getAddedTag() { public T getAddedTag() {
return getNewValue(); return getNewValue();
} }
@Override @Override
public T getNewValue() { public T getNewValue() {
/** /**
@ -95,6 +108,21 @@ abstract class TagAddedEvent<T extends Tag> extends AutopsyEvent implements Seri
return null; 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<V> getDeletedTags() {
return deletedTagInfoList != null ? Collections.unmodifiableList(deletedTagInfoList) : null;
}
@Override
public Object getOldValue() {
return getDeletedTags();
}
/** /**
* implementors should override this to lookup the appropriate kind of tag * implementors should override this to lookup the appropriate kind of tag

View File

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

View File

@ -481,7 +481,7 @@ public class TagsManager implements Closeable {
try { try {
Case currentCase = Case.getCurrentCaseThrows(); 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) { } catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", 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); TaggingManager.BlackboardArtifactTagChange tagChange = caseDb.getTaggingManager().addArtifactTag(artifact, tagName, comment);
try { try {
Case currentCase = Case.getCurrentCaseThrows(); 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) { } catch (NoCurrentCaseException ex) {
throw new TskCoreException("Added a tag to a closed case", ex); throw new TskCoreException("Added a tag to a closed case", ex);
} }

View File

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

View File

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

View File

@ -657,6 +657,17 @@ public final class EventsModel {
} }
return postTagsDeleted(updatedEventIDs); 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 * Updates the events model for an artifact tag deleted event and publishes
@ -782,7 +793,6 @@ public final class EventsModel {
* @throws TskCoreException * @throws TskCoreException
*/ */
public synchronized void invalidateCaches(Collection<Long> updatedEventIDs) throws TskCoreException { public synchronized void invalidateCaches(Collection<Long> updatedEventIDs) throws TskCoreException {
populateDataSourcesCache();
minEventTimeCache.invalidateAll(); minEventTimeCache.invalidateAll();
maxEventTimeCache.invalidateAll(); maxEventTimeCache.invalidateAll();
idsToEventsCache.invalidateAll(emptyIfNull(updatedEventIDs)); idsToEventsCache.invalidateAll(emptyIfNull(updatedEventIDs));

View File

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

View File

@ -79,6 +79,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
@ -107,6 +108,8 @@ public final class ImageGalleryController {
Case.Events.CONTENT_TAG_DELETED, Case.Events.CONTENT_TAG_DELETED,
Case.Events.DATA_SOURCE_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 * 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 { void startUp() throws TskCoreException {
selectionModel = new FileIDSelectionModel(this); selectionModel = new FileIDSelectionModel(this);
thumbnailCache = new ThumbnailCache(this); thumbnailCache = new ThumbnailCache(this);
TagSet categoryTagSet = getCategoryTagSet();
/* /*
* TODO (JIRA-5212): The next two lines need to be executed in this * TODO (JIRA-5212): The next two lines need to be executed in this
* order. Why? This suggests there is some inappropriate coupling * order. Why? This suggests there is some inappropriate coupling
* between the DrawableDB and GroupManager classes. * between the DrawableDB and GroupManager classes.
*/ */
groupManager = new GroupManager(this); groupManager = new GroupManager(this);
drawableDB = DrawableDB.getDrawableDB(this); drawableDB = DrawableDB.getDrawableDB(this, categoryTagSet);
categoryManager = new CategoryManager(this); categoryManager = new CategoryManager(this, categoryTagSet);
tagsManager = new DrawableTagsManager(this); tagsManager = new DrawableTagsManager(this);
tagsManager.registerListener(groupManager); tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager); tagsManager.registerListener(categoryManager);
@ -720,6 +725,28 @@ public final class ImageGalleryController {
private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException { private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile); return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
} }
/**
* Returns the TagSet with the image gallery categories.
*
* @return Category TagSet.
*
* @throws TskCoreException
*/
private TagSet getCategoryTagSet() throws TskCoreException {
List<TagSet> tagSetList = getCaseDatabase().getTaggingManager().getTagSets();
if (tagSetList != null && !tagSetList.isEmpty()) {
for (TagSet set : tagSetList) {
if (set.getName().startsWith(CATEGORY_TAG_SET_PREFIX)) {
return set;
}
}
// If we get to here the Project VIC Test set wasn't found;
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
} else {
throw new TskCoreException("Error loading Project VIC tag set: Tag set not found.");
}
}
/** /**
* A listener for ingest module application events. * A listener for ingest module application events.

View File

@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException; 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 { 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 Logger.getLogger(UpdateDrawableFileTask.class.getName()).log(Level.SEVERE, "Error in update file task", ex); //NON-NLS
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -24,8 +24,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.atomic.LongAdder;
import java.util.logging.Level; import java.util.logging.Level;
@ -33,11 +36,13 @@ import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; 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.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TagSet;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -56,8 +61,6 @@ public class CategoryManager {
private static final Logger LOGGER = Logger.getLogger(CategoryManager.class.getName()); 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 * the DrawableDB that backs the category counts cache. The counts are
* initialized from this, and the counting of CAT-0 is always delegated to * 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 DrawableDB drawableDb;
private final TagSet categoryTagSet;
/** /**
* Used to distribute CategoryChangeEvents * Used to distribute CategoryChangeEvents
*/ */
@ -79,32 +84,20 @@ public class CategoryManager {
* the count related methods go through this cache, which loads initial * the count related methods go through this cache, which loads initial
* values from the database if needed. * values from the database if needed.
*/ */
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts private final LoadingCache<TagName, LongAdder> categoryCounts
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper)); = CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
/**
* cached TagNames corresponding to Categories, looked up from
* autopsyTagManager at initial request or if invalidated by case change.
*/
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
@Override
public TagName load(DhsImageCategory cat) throws TskCoreException {
return getController().getTagsManager().getTagName(cat);
}
});
public CategoryManager(ImageGalleryController controller) { public CategoryManager(ImageGalleryController controller, TagSet categoryTagSet) throws TskCoreException {
this.controller = controller;
this.drawableDb = controller.getDrawablesDatabase(); this.drawableDb = controller.getDrawablesDatabase();
this.categoryTagSet = categoryTagSet;
} }
private ImageGalleryController getController() { public List<TagName> getCategories() {
return controller; return Collections.unmodifiableList(getSortedTagNames(categoryTagSet.getTagNames()));
} }
synchronized public void invalidateCaches() { synchronized public void invalidateCaches() {
categoryCounts.invalidateAll(); categoryCounts.invalidateAll();
catTagNameMap.invalidateAll();
fireChange(Collections.emptyList(), null); fireChange(Collections.emptyList(), null);
} }
@ -115,16 +108,8 @@ public class CategoryManager {
* *
* @return the number of files with the given Category * @return the number of files with the given Category
*/ */
synchronized public long getCategoryCount(DhsImageCategory cat) { synchronized public long getCategoryCount(TagName tagName) {
if (cat == DhsImageCategory.ZERO) { return categoryCounts.getUnchecked(tagName).sum();
// 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();
}
} }
/** /**
@ -133,10 +118,8 @@ public class CategoryManager {
* *
* @param cat the Category to increment * @param cat the Category to increment
*/ */
synchronized public void incrementCategoryCount(DhsImageCategory cat) { synchronized public void incrementCategoryCount(TagName tagName) {
if (cat != DhsImageCategory.ZERO) { categoryCounts.getUnchecked(tagName).increment();
categoryCounts.getUnchecked(cat).increment();
}
} }
/** /**
@ -145,10 +128,8 @@ public class CategoryManager {
* *
* @param cat the Category to decrement * @param cat the Category to decrement
*/ */
synchronized public void decrementCategoryCount(DhsImageCategory cat) { synchronized public void decrementCategoryCount(TagName tagName) {
if (cat != DhsImageCategory.ZERO) { categoryCounts.getUnchecked(tagName).decrement();
categoryCounts.getUnchecked(cat).decrement();
}
} }
/** /**
@ -161,14 +142,14 @@ public class CategoryManager {
* @return a LongAdder whose value is set to the number of file with the * @return a LongAdder whose value is set to the number of file with the
* given Category * given Category
*/ */
synchronized private LongAdder getCategoryCountHelper(DhsImageCategory cat) { synchronized private LongAdder getCategoryCountHelper(TagName cat) {
LongAdder longAdder = new LongAdder(); LongAdder longAdder = new LongAdder();
longAdder.decrement(); longAdder.decrement();
try { try {
longAdder.add(drawableDb.getCategoryCount(cat)); longAdder.add(drawableDb.getCategoryCount(cat));
longAdder.increment(); longAdder.increment();
} catch (IllegalStateException ex) { } 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; return longAdder;
} }
@ -178,8 +159,8 @@ public class CategoryManager {
* *
* @param fileIDs * @param fileIDs
*/ */
public void fireChange(Collection<Long> fileIDs, DhsImageCategory newCategory) { public void fireChange(Collection<Long> fileIDs, TagName tagName) {
categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory)); 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) { public boolean isCategoryTagName(TagName tName) {
return catTagNameMap.getUnchecked(cat); 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()); * Returns the category tag set.
} *
* @return
public static boolean isCategoryTagName(TagName tName) { */
return DhsImageCategory.isCategoryName(tName.getDisplayName()); TagSet getCategorySet() {
} return categoryTagSet;
public static boolean isNotCategoryTagName(TagName tName) {
return DhsImageCategory.isNotCategoryName(tName.getDisplayName());
} }
@Subscribe @Subscribe
public void handleTagAdded(ContentTagAddedEvent event) { public void handleTagAdded(ContentTagAddedEvent event) {
final ContentTag addedTag = event.getAddedTag(); 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<DeletedContentTagInfo> 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 @Subscribe
public void handleTagDeleted(ContentTagDeletedEvent event) { public void handleTagDeleted(ContentTagDeletedEvent event) {
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo(); final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
handleDeletedInfo(deletedTagInfo);
}
private void handleDeletedInfo(DeletedContentTagInfo deletedTagInfo) {
TagName tagName = deletedTagInfo.getName(); TagName tagName = deletedTagInfo.getName();
if (isCategoryTagName(tagName)) { if (isCategoryTagName(tagName)) {
decrementCategoryCount(tagName);
DhsImageCategory deletedCat = CategoryManager.categoryFromTagName(tagName);
if (deletedCat != DhsImageCategory.ZERO) {
decrementCategoryCount(deletedCat);
}
fireChange(Collections.singleton(deletedTagInfo.getContentID()), null); fireChange(Collections.singleton(deletedTagInfo.getContentID()), null);
} }
} }
@ -290,16 +269,16 @@ public class CategoryManager {
public static class CategoryChangeEvent { public static class CategoryChangeEvent {
private final ImmutableSet<Long> fileIDs; private final ImmutableSet<Long> fileIDs;
private final DhsImageCategory newCategory; private final TagName tagName;
public CategoryChangeEvent(Collection<Long> fileIDs, DhsImageCategory newCategory) { public CategoryChangeEvent(Collection<Long> fileIDs, TagName tagName) {
super(); super();
this.fileIDs = ImmutableSet.copyOf(fileIDs); this.fileIDs = ImmutableSet.copyOf(fileIDs);
this.newCategory = newCategory; this.tagName = tagName;
} }
public DhsImageCategory getNewCategory() { public TagName getNewCategory() {
return newCategory; return tagName;
} }
/** /**
@ -309,4 +288,18 @@ public class CategoryManager {
return fileIDs; return fileIDs;
} }
} }
private List<TagName> getSortedTagNames(List<TagName> tagNames) {
Comparator<TagName> compareByDisplayName = new Comparator<TagName>() {
@Override
public int compare(TagName tagName1, TagName tagName2) {
return tagName1.getDisplayName().compareTo(tagName2.getDisplayName());
}
};
List<TagName> sortedTagNames = new ArrayList<>(tagNames);
sortedTagNames.sort(compareByDisplayName);
return sortedTagNames;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -59,6 +59,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
public String getValueDisplayName() { public String getValueDisplayName() {
return Objects.equals(attr, DrawableAttribute.TAGS) return Objects.equals(attr, DrawableAttribute.TAGS)
|| Objects.equals(attr, DrawableAttribute.CATEGORY)
? ((TagName) getValue()).getDisplayName() ? ((TagName) getValue()).getDisplayName()
: Objects.toString(getValue(), "unknown"); : Objects.toString(getValue(), "unknown");
} }
@ -74,8 +75,9 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
hash = 79 * hash + Objects.hashCode(this.val); hash = 79 * hash + Objects.hashCode(this.val);
hash = 79 * hash + Objects.hashCode(this.attr); hash = 79 * hash + Objects.hashCode(this.attr);
if (this.dataSource != null) if (this.dataSource != null) {
hash = 79 * hash + (int)this.dataSource.getId(); hash = 79 * hash + (int) this.dataSource.getId();
}
return hash; return hash;
} }
@ -99,20 +101,20 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
if (!Objects.equals(this.attr, other.attr)) { if (!Objects.equals(this.attr, other.attr)) {
return false; return false;
} }
// Data source is significant only for PATH based groups. // Data source is significant only for PATH based groups.
if (this.attr == DrawableAttribute.PATH) { if (this.attr == DrawableAttribute.PATH) {
if (this.dataSource != null && other.dataSource != null) { 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) { } else if (this.dataSource == null && other.dataSource == null) {
// neither group has a datasource // neither group has a datasource
return true; return true;
} else { } else {
// one group has a datasource, other doesn't // one group has a datasource, other doesn't
return false; return false;
} }
} }
return true; return true;
} }

View File

@ -62,7 +62,6 @@ import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder; import javax.swing.SortOrder;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual; import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; 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.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; 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.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@ -106,23 +103,23 @@ public class GroupManager {
private final ImageGalleryController controller; private final ImageGalleryController controller;
/** /**
* Keeps track of the current path group * Keeps track of the current path group - a change in path indicates the
* - a change in path indicates the current path group is analyzed * current path group is analyzed
*/ */
@GuardedBy("this") //NOPMD @GuardedBy("this") //NOPMD
private GroupKey<?> currentPathGroup = null; private GroupKey<?> currentPathGroup = null;
/** /**
* list of all analyzed groups - i.e. groups that are ready to be shown to user. * list of all analyzed groups - i.e. groups that are ready to be shown to
* These are groups under the selected groupBy attribute. * user. These are groups under the selected groupBy attribute.
*/ */
@GuardedBy("this") //NOPMD @GuardedBy("this") //NOPMD
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList(); private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
/** /**
* list of unseen groups * list of unseen groups These are groups under the selected groupBy
* These are groups under the selected groupBy attribute. * attribute.
*/ */
@GuardedBy("this") //NOPMD @GuardedBy("this") //NOPMD
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList(); private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
@ -186,15 +183,15 @@ public class GroupManager {
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
synchronized public Set<GroupKey<?>> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException { synchronized public Set<GroupKey<?>> getAllGroupKeysForFile(DrawableFile file) throws TskCoreException, TskDataException {
Set<GroupKey<?>> resultSet = new HashSet<>(); Set<GroupKey<?>> resultSet = new HashSet<>();
for (DrawableAttribute<?> attr: DrawableAttribute.getGroupableAttrs()) { for (DrawableAttribute<?> attr : DrawableAttribute.getGroupableAttrs()) {
for (Comparable<?> val : attr.getValue(file)) { for (Comparable<?> val : attr.getValue(file)) {
if (attr == DrawableAttribute.PATH) { if (attr == DrawableAttribute.PATH) {
resultSet.add(new GroupKey(attr, val, file.getDataSource())); resultSet.add(new GroupKey(attr, val, file.getDataSource()));
} else if (attr == DrawableAttribute.TAGS) { } else if (attr == DrawableAttribute.TAGS) {
//don't show groups for the categories when grouped by 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)); resultSet.add(new GroupKey(attr, val, null));
} }
} else { } else {
@ -204,9 +201,8 @@ public class GroupManager {
} }
return resultSet; return resultSet;
} }
/** /**
* *
* Returns GroupKeys for all the Groups the given file is a part of. * Returns GroupKeys for all the Groups the given file is a part of.
* *
@ -225,7 +221,7 @@ public class GroupManager {
} }
return Collections.emptySet(); return Collections.emptySet();
} }
/** /**
* @param groupKey * @param groupKey
* *
@ -244,7 +240,7 @@ public class GroupManager {
setGroupBy(DrawableAttribute.PATH); setGroupBy(DrawableAttribute.PATH);
setSortOrder(SortOrder.ASCENDING); setSortOrder(SortOrder.ASCENDING);
setDataSource(null); setDataSource(null);
unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener);
unSeenGroups.clear(); unSeenGroups.clear();
analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener);
@ -300,12 +296,12 @@ public class GroupManager {
public ListenableFuture<?> markGroupUnseen(DrawableGroup group) { public ListenableFuture<?> markGroupUnseen(DrawableGroup group) {
return exec.submit(() -> { return exec.submit(() -> {
try { try {
getDrawableDB().markGroupUnseen(group.getGroupKey()); getDrawableDB().markGroupUnseen(group.getGroupKey());
// only update and reshuffle if its new results // only update and reshuffle if its new results
if (group.isSeen() == true) { if (group.isSeen() == true) {
group.setSeen(false); group.setSeen(false);
} }
// The group may already be in 'unseen' state, e.g. when new files are added, // The group may already be in 'unseen' state, e.g. when new files are added,
// but not be on the unseenGroupsList yet. // but not be on the unseenGroupsList yet.
updateUnSeenGroups(group); updateUnSeenGroups(group);
@ -314,7 +310,7 @@ public class GroupManager {
} }
}); });
} }
/** /**
* Update unseenGroups list accordingly based on the current status of * Update unseenGroups list accordingly based on the current status of
* 'group'. Removes it if it is seen or adds it if it is unseen. * 'group'. Removes it if it is seen or adds it if it is unseen.
@ -322,13 +318,13 @@ public class GroupManager {
* @param group * @param group
*/ */
synchronized private void updateUnSeenGroups(DrawableGroup group) { synchronized private void updateUnSeenGroups(DrawableGroup group) {
if (group.isSeen()) { if (group.isSeen()) {
unSeenGroups.removeAll(group); unSeenGroups.removeAll(group);
} else if (unSeenGroups.contains(group) == false && } else if (unSeenGroups.contains(group) == false
getGroupBy() == group.getGroupKey().getAttribute()) { && getGroupBy() == group.getGroupKey().getAttribute()) {
unSeenGroups.add(group); unSeenGroups.add(group);
} }
sortUnseenGroups(); sortUnseenGroups();
} }
/** /**
@ -390,7 +386,7 @@ public class GroupManager {
switch (groupKey.getAttribute().attrName) { switch (groupKey.getAttribute().attrName) {
//these cases get special treatment //these cases get special treatment
case CATEGORY: case CATEGORY:
return getFileIDsWithCategory((DhsImageCategory) groupKey.getValue()); return getFileIDsWithCategory((TagName) groupKey.getValue());
case TAGS: case TAGS:
return getFileIDsWithTag((TagName) groupKey.getValue()); return getFileIDsWithTag((TagName) groupKey.getValue());
case MIME_TYPE: case MIME_TYPE:
@ -405,33 +401,18 @@ public class GroupManager {
// @@@ This was kind of slow in the profiler. Maybe we should cache it. // @@@ 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. // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
synchronized public Set<Long> getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { synchronized public Set<Long> getFileIDsWithCategory(TagName category) throws TskCoreException {
Set<Long> fileIDsToReturn = Collections.emptySet(); Set<Long> fileIDsToReturn = Collections.emptySet();
try { try {
final DrawableTagsManager tagsManager = controller.getTagsManager(); final DrawableTagsManager tagsManager = controller.getTagsManager();
if (category == DhsImageCategory.ZERO) {
Set<Long> fileIDs = new HashSet<>();
for (TagName catTagName : tagsManager.getCategoryTagNames()) {
if (notEqual(catTagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) {
tagsManager.getContentTagsByTagName(catTagName).stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
.map(ct -> ct.getContent().getId())
.filter(getDrawableDB()::isInDB)
.forEach(fileIDs::add);
}
}
fileIDsToReturn = getDrawableDB().findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(category);
} else { fileIDsToReturn = contentTags.stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); .filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
fileIDsToReturn = contentTags.stream() .map(ct -> ct.getContent().getId())
.filter(ct -> ct.getContent() instanceof AbstractFile) .collect(Collectors.toSet());
.filter(ct -> getDrawableDB().isInDB(ct.getContent().getId()))
.map(ct -> ct.getContent().getId())
.collect(Collectors.toSet());
}
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS
throw ex; throw ex;
@ -552,14 +533,14 @@ public class GroupManager {
synchronized public void handleTagAdded(ContentTagAddedEvent evt) { synchronized public void handleTagAdded(ContentTagAddedEvent evt) {
GroupKey<?> newGroupKey = null; GroupKey<?> newGroupKey = null;
final long fileID = evt.getAddedTag().getContent().getId(); final long fileID = evt.getAddedTag().getContent().getId();
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) { if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(evt.getAddedTag().getName())) {
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource()); newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, evt.getAddedTag().getName(), getDataSource());
for (GroupKey<?> oldGroupKey : groupMap.keySet()) { for (GroupKey<?> oldGroupKey : groupMap.keySet()) {
if (oldGroupKey.equals(newGroupKey) == false) { if (oldGroupKey.equals(newGroupKey) == false) {
removeFromGroup(oldGroupKey, fileID); 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()); newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource());
} }
if (newGroupKey != null) { 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 * @param group Group being added to (will be null if a group has not yet
* been created) * been created)
@ -584,16 +566,20 @@ public class GroupManager {
//if there wasn't already a DrawableGroup, then check if this group is now //if there wasn't already a DrawableGroup, then check if this group is now
// in an appropriate state to get one made. // in an appropriate state to get one made.
// Path group, for example, only gets a DrawableGroup created when all files are analyzed // 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 * NOTE: With the current (Jan 2019) behavior of how we detect a
* is added to the group, the call to 'populateIfAnalyzed' will still not return a group and therefore this * PATH group as being analyzed, the group is not marked as analyzed
* method will never mark the group as unseen. */ * 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); group = popuplateIfAnalyzed(groupKey, null);
} else { } else {
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it. //if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
group.addFile(fileID); group.addFile(fileID);
} }
// reset the seen status for the group (if it is currently considered analyzed) // reset the seen status for the group (if it is currently considered analyzed)
if (group != null) { if (group != null) {
markGroupUnseen(group); markGroupUnseen(group);
@ -605,18 +591,14 @@ public class GroupManager {
GroupKey<?> groupKey = null; GroupKey<?> groupKey = null;
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
final TagName deletedTagName = deletedTagInfo.getName(); final TagName deletedTagName = deletedTagInfo.getName();
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(deletedTagName)) { if (getGroupBy() == DrawableAttribute.CATEGORY && controller.getCategoryManager().isCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(deletedTagName), null); groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, deletedTagName, null);
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(deletedTagName)) { } else if (getGroupBy() == DrawableAttribute.TAGS && controller.getCategoryManager().isNotCategoryTagName(deletedTagName)) {
groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null); groupKey = new GroupKey<>(DrawableAttribute.TAGS, deletedTagName, null);
} }
if (groupKey != null) { if (groupKey != null) {
final long fileID = deletedTagInfo.getContentID(); final long fileID = deletedTagInfo.getContentID();
DrawableGroup g = removeFromGroup(groupKey, fileID); 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 { try {
DrawableFile file = getDrawableDB().getFileFromID(fileId); DrawableFile file = getDrawableDB().getFileFromID(fileId);
String pathVal = file.getDrawablePath(); String pathVal = file.getDrawablePath();
GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH,pathVal, file.getDataSource()); GroupKey<?> pathGroupKey = new GroupKey<>(DrawableAttribute.PATH, pathVal, file.getDataSource());
updateCurrentPathGroup(pathGroupKey); updateCurrentPathGroup(pathGroupKey);
} catch (TskCoreException | TskDataException ex) { } catch (TskCoreException | TskDataException ex) {
logger.log(Level.WARNING, "Error getting drawabledb for fileId " + fileId, ex); logger.log(Level.WARNING, "Error getting drawabledb for fileId " + fileId, ex);
} }
// Update all the groups that this file belongs to // Update all the groups that this file belongs to
Set<GroupKey<?>> groupsForFile = getAllGroupKeysForFile(fileId); Set<GroupKey<?>> groupsForFile = getAllGroupKeysForFile(fileId);
for (GroupKey<?> gk : groupsForFile) { 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 //we fire this event for all files so that the category counts get updated during initial db population
controller.getCategoryManager().fireChange(updatedFileIDs, null); controller.getCategoryManager().fireChange(updatedFileIDs, null);
} }
/** /**
* Checks if the given path is different from the current path group. * Checks if the given path is different from the current path group. If so,
* If so, updates the current path group as analyzed, and sets current path * updates the current path group as analyzed, and sets current path group
* group to the given path. * to the given path.
* *
* The idea is that when the path of the files being processed changes, * The idea is that when the path of the files being processed changes, we
* we have moved from one folder to the next, and the group for the * have moved from one folder to the next, and the group for the previous
* previous PATH can be considered as analyzed and can be displayed. * 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, * NOTE: this a close approximation for when all files in a folder have been
* but there's some room for error - files may go down the ingest pipleline * processed, but there's some room for error - files may go down the ingest
* out of order or the events may not always arrive in the same order * pipleline out of order or the events may not always arrive in the same
* * order
* @param groupKey *
* @param groupKey
*/ */
synchronized private void updateCurrentPathGroup(GroupKey<?> groupKey) { synchronized private void updateCurrentPathGroup(GroupKey<?> groupKey) {
try { try {
if (groupKey.getAttribute() == DrawableAttribute.PATH) { if (groupKey.getAttribute() == DrawableAttribute.PATH) {
if (this.currentPathGroup == null) { if (this.currentPathGroup == null) {
currentPathGroup = groupKey; 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 // mark the last path group as analyzed
getDrawableDB().markGroupAnalyzed(currentPathGroup); getDrawableDB().markGroupAnalyzed(currentPathGroup);
popuplateIfAnalyzed(currentPathGroup, null); popuplateIfAnalyzed(currentPathGroup, null);
currentPathGroup = groupKey; 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 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() { synchronized public void resetCurrentPathGroup() {
try { try {
@ -719,11 +701,11 @@ public class GroupManager {
popuplateIfAnalyzed(currentPathGroup, null); popuplateIfAnalyzed(currentPathGroup, null);
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 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 * 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 * 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); controller.getCategoryManager().registerListener(group);
groupMap.put(groupKey, group); groupMap.put(groupKey, group);
} }
// Add to analyzedGroups only if it's the a group with the selected groupBy attribute // Add to analyzedGroups only if it's the a group with the selected groupBy attribute
if ((analyzedGroups.contains(group) == false) && if ((analyzedGroups.contains(group) == false)
(getGroupBy() == group.getGroupKey().getAttribute())) { && (getGroupBy() == group.getGroupKey().getAttribute())) {
analyzedGroups.add(group); analyzedGroups.add(group);
sortAnalyzedGroups(); sortAnalyzedGroups();
} }
updateUnSeenGroups(group); updateUnSeenGroups(group);
@ -944,11 +926,11 @@ public class GroupManager {
switch (groupBy.attrName) { switch (groupBy.attrName) {
//these cases get special treatment //these cases get special treatment
case CATEGORY: case CATEGORY:
results.putAll(null, Arrays.asList(DhsImageCategory.values())); results.putAll(null, controller.getCategoryManager().getCategories());
break; break;
case TAGS: case TAGS:
results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream()
.filter(CategoryManager::isNotCategoryTagName) .filter(controller.getCategoryManager()::isNotCategoryTagName)
.collect(Collectors.toList())); .collect(Collectors.toList()));
break; break;

View File

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

View File

@ -56,7 +56,7 @@ public final class GuiUtils {
/** /**
* Create a MenuItem that performs the given action and also set the Action * 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. * remember the last chosen menu item as its action.
* *
* @param button * @param button

View File

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

View File

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

View File

@ -20,6 +20,8 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform; 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.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; 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 * 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 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)); Map<String, Border> BORDER_MAP = new HashMap<>();
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));
Region getCategoryBorderRegion(); Region getCategoryBorderRegion();
@ -115,38 +108,38 @@ public interface DrawableView {
} }
static Border getCategoryBorder(DhsImageCategory category) { /**
if (category != null) { * Get the boarder for the given category.
switch (category) { *
case ONE: * Static instances of the boarders will lazily constructed and stored in
return CAT1_BORDER; * the BORDER_MAP.
case TWO: *
return CAT2_BORDER; * @param category
case THREE: *
return CAT3_BORDER; * @return
case FOUR: */
return CAT4_BORDER; static Border getCategoryBorder(TagName category) {
case FIVE: Border border = null;
return CAT5_BORDER; if (category != null && category.getColor() != HTML_COLOR.NONE) {
case ZERO: border = BORDER_MAP.get(category.getDisplayName());
default:
return CAT0_BORDER;
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) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
default DhsImageCategory updateCategory() { default TagName updateCategory() {
if (getFile().isPresent()) { if (getFile().isPresent()) {
final DhsImageCategory category = getFile().map(DrawableFile::getCategory).orElse(DhsImageCategory.ZERO); final TagName tagNameCat = getFile().map(DrawableFile::getCategory).orElse(null);
final Border border = hasHashHit() && (category == DhsImageCategory.ZERO) ? HASH_BORDER : getCategoryBorder(category); final Border border = hasHashHit() ? HASH_BORDER : getCategoryBorder(tagNameCat);
Platform.runLater(() -> getCategoryBorderRegion().setBorder(border)); Platform.runLater(() -> getCategoryBorderRegion().setBorder(border));
return category; return tagNameCat;
} else { } else {
return DhsImageCategory.ZERO; return null;
} }
} }
} }

View File

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

View File

@ -19,12 +19,10 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import static com.google.common.collect.Lists.transform; import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -50,7 +48,6 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -86,10 +83,7 @@ import static javafx.scene.input.KeyCode.UP;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths; import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii; import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.HBox; 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.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; 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 static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback; import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -176,19 +170,6 @@ public class GroupPane extends BorderPane {
private SplitMenuButton tagSelectedSplitMenu; private SplitMenuButton tagSelectedSplitMenu;
@FXML @FXML
private ToolBar headerToolBar; 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 @FXML
private SegmentedButton segButton; private SegmentedButton segButton;
@ -220,11 +201,6 @@ public class GroupPane extends BorderPane {
@FXML @FXML
private Label catContainerLabel; private Label catContainerLabel;
@FXML @FXML
private Label catHeadingLabel;
@FXML
private HBox catSegmentedContainer;
@FXML
private HBox catSplitMenuContainer; private HBox catSplitMenuContainer;
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class); private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
@ -244,12 +220,18 @@ public class GroupPane extends BorderPane {
private ContextMenu contextMenu; private ContextMenu contextMenu;
/** the current GroupViewMode of this GroupPane */ /**
* the current GroupViewMode of this GroupPane
*/
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE); private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
/** the grouping this pane is currently the view for */ /**
* the grouping this pane is currently the view for
*/
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
private final Map<String, ToggleButton> toggleButtonMap = new HashMap<>();
/** /**
* Map from fileIDs to their assigned cells in the tile view. This is used * 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 * to determine whether fileIDs are visible or are offscreen. No entry
@ -278,7 +260,7 @@ public class GroupPane extends BorderPane {
undoAction = new UndoAction(controller); undoAction = new UndoAction(controller);
redoAction = new RedoAction(controller); redoAction = new RedoAction(controller);
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
} }
GroupViewMode getGroupViewMode() { GroupViewMode getGroupViewMode() {
@ -307,7 +289,35 @@ public class GroupPane extends BorderPane {
} }
void syncCatToggle(DrawableFile file) { 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() { public void activateTileViewer() {
@ -353,25 +363,6 @@ public class GroupPane extends BorderPane {
return grouping.getReadOnlyProperty(); 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. * called automatically during constructor by FXMLConstructor.
* *
@ -384,12 +375,6 @@ public class GroupPane extends BorderPane {
"GroupPane.catContainerLabel.displayText=Categorize Selected File:", "GroupPane.catContainerLabel.displayText=Categorize Selected File:",
"GroupPane.catHeadingLabel.displayText=Category:"}) "GroupPane.catHeadingLabel.displayText=Category:"})
void initialize() { 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 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 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'."; 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 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'."; 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 //configure flashing glow animation on next unseen group button
flashAnimation.setCycleCount(Timeline.INDEFINITE); flashAnimation.setCycleCount(Timeline.INDEFINITE);
flashAnimation.setAutoReverse(true); flashAnimation.setAutoReverse(true);
@ -447,14 +417,14 @@ public class GroupPane extends BorderPane {
}, },
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS 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.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic()); catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()), List<MenuItem> categoryMenues = transform(controller.getCategoryManager().getCategories(),
cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller))); cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
catSelectedSplitMenu.getItems().setAll(categoryMenues); catSelectedSplitMenu.getItems().setAll(categoryMenues);
@ -466,16 +436,21 @@ public class GroupPane extends BorderPane {
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
catHeadingLabel.setText(Bundle.GroupPane_catHeadingLabel_displayText());
//show categorization controls depending on group view mode // This seems to be the only way to make sure the when the user switches
headerToolBar.getItems().remove(catSegmentedContainer); // to SLIDE_SHOW the first time that the undo\redo buttons are removed.
headerToolBar.getItems().remove(undoButton);
headerToolBar.getItems().remove(redoButton);
headerToolBar.getItems().add(undoButton);
headerToolBar.getItems().add(redoButton);
groupViewMode.addListener((ObservableValue<? extends GroupViewMode> observable, GroupViewMode oldValue, GroupViewMode newValue) -> { groupViewMode.addListener((ObservableValue<? extends GroupViewMode> observable, GroupViewMode oldValue, GroupViewMode newValue) -> {
if (newValue == GroupViewMode.SLIDE_SHOW) { if (newValue == GroupViewMode.SLIDE_SHOW) {
headerToolBar.getItems().remove(catSplitMenuContainer); headerToolBar.getItems().remove(undoButton);
headerToolBar.getItems().add(catSegmentedContainer); headerToolBar.getItems().remove(redoButton);
} else { } else {
headerToolBar.getItems().remove(catSegmentedContainer); headerToolBar.getItems().add(undoButton);
headerToolBar.getItems().add(catSplitMenuContainer); 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 //listen to tile selection and make sure it is visible in scroll area
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
&& slideShowPane != null) { && slideShowPane != null) {
slideShowPane.setFile(newFileId); slideShowPane.setFile(newFileId);
} else { } else {
scrollToFileID(newFileId); scrollToFileID(newFileId);
@ -775,42 +750,9 @@ public class GroupPane extends BorderPane {
selectAllFiles(); selectAllFiles();
t.consume(); t.consume();
} }
ObservableSet<Long> selected = selectionModel.getSelected();
if (selected.isEmpty() == false) {
DhsImageCategory cat = keyCodeToCat(t.getCode());
if (cat != null) {
new CategorizeAction(controller, cat, selected).handle(null);
}
}
} }
} }
private DhsImageCategory keyCodeToCat(KeyCode t) {
if (t != null) {
switch (t) {
case NUMPAD0:
case DIGIT0:
return DhsImageCategory.ZERO;
case NUMPAD1:
case DIGIT1:
return DhsImageCategory.ONE;
case NUMPAD2:
case DIGIT2:
return DhsImageCategory.TWO;
case NUMPAD3:
case DIGIT3:
return DhsImageCategory.THREE;
case NUMPAD4:
case DIGIT4:
return DhsImageCategory.FOUR;
case NUMPAD5:
case DIGIT5:
return DhsImageCategory.FIVE;
}
}
return null;
}
private void handleArrows(KeyEvent t) { private void handleArrows(KeyEvent t) {
Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); Long lastSelectFileId = selectionModel.lastSelectedProperty().get();

View File

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

View File

@ -50,12 +50,12 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; 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.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer; 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.DrawableUIBase.exec;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH; 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 * 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 @Override
@ThreadConfined(type = ThreadType.ANY) @ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() { public TagName updateCategory() {
Optional<DrawableFile> file = getFile(); Optional<DrawableFile> file = getFile();
if (file.isPresent()) { if (file.isPresent()) {
DhsImageCategory updateCategory = super.updateCategory(); TagName updateCategory = super.updateCategory();
Platform.runLater(() -> getGroupPane().syncCatToggle(file.get())); Platform.runLater(() -> getGroupPane().syncCatToggle(file.get()));
return updateCategory; return updateCategory;
} else { } else {
return DhsImageCategory.ZERO; return null;
} }
} }

View File

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

View File

@ -114,12 +114,8 @@ final class ExtractSru extends Extract {
AbstractFile sruAbstractFile = getSruFile(dataSource, tempDirPath); AbstractFile sruAbstractFile = getSruFile(dataSource, tempDirPath);
String sruFileName = tempDirPath + File.separator + sruAbstractFile.getId() + "_" + sruAbstractFile.getName(); if (sruAbstractFile == null) {
return; //If we cannot find the srudb.dat file we cannot proceed which is ok
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
} }
final String sruDumper = getPathForSruDumper(); final String sruDumper = getPathForSruDumper();
@ -135,6 +131,7 @@ final class ExtractSru extends Extract {
try { try {
String modOutFile = modOutPath + File.separator + sruAbstractFile.getId() + "_srudb.db3"; String modOutFile = modOutPath + File.separator + sruAbstractFile.getId() + "_srudb.db3";
String sruFileName = tempDirPath + File.separator + sruAbstractFile.getId() + "_" + sruAbstractFile.getName();
extractSruFiles(sruDumper, sruFileName, modOutFile, tempDirPath, softwareHiveFileName); extractSruFiles(sruDumper, sruFileName, modOutFile, tempDirPath, softwareHiveFileName);