diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED index b31ec5db26..102aaf6890 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/Bundle.properties-MERGED @@ -1,9 +1,11 @@ DiscoveryAttributes.GroupingAttributeType.datasource.displayName=Data Source DiscoveryAttributes.GroupingAttributeType.fileType.displayName=File Type +DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date DiscoveryAttributes.GroupingAttributeType.frequency.displayName=Past Occurrences DiscoveryAttributes.GroupingAttributeType.hash.displayName=Hash Set DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item DiscoveryAttributes.GroupingAttributeType.keywordList.displayName=Keyword +DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date DiscoveryAttributes.GroupingAttributeType.none.displayName=None DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected DiscoveryAttributes.GroupingAttributeType.parent.displayName=Parent Folder @@ -15,15 +17,17 @@ DiscoveryKeyUtils.DataSourceGroupKey.datasourceAndID={0}(ID: {1}) # {0} - Data source ID DiscoveryKeyUtils.DataSourceGroupKey.idOnly=Data source (ID: {0}) DiscoveryKeyUtils.FileTagGroupKey.noSets=None +DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available +DiscoveryKeyUtils.HashHitsGroupKey.noHashHits=None +DiscoveryKeyUtils.InterestingItemGroupKey.noSets=None +DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None +DiscoveryKeyUtils.MostRecentActivityDateGroupKey.noDate=No Date Available DiscoveryKeyUtils.NoGroupingGroupKey.allFiles=All Files +DiscoveryKeyUtils.ObjectDetectedGroupKey.noSets=None FileGroup.groupSortingAlgorithm.groupName.text=Group Name FileGroup.groupSortingAlgorithm.groupSize.text=Group Size FileSearch.documentSummary.noBytes=No bytes read for document, unable to display preview. FileSearch.documentSummary.noPreview=No preview available. -FileSearch.HashHitsGroupKey.noHashHits=None -FileSearch.InterestingItemGroupKey.noSets=None -FileSearch.KeywordListGroupKey.noKeywords=None -FileSearch.ObjectDetectedGroupKey.noSets=None FileSearchFiltering.concatenateSetNamesForDisplay.comma=, # {0} - filters FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java index ca95a16d08..59e620828a 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryAttributes.java @@ -222,7 +222,7 @@ public class DiscoveryAttributes { static class FrequencyAttribute extends AttributeType { static final int BATCH_SIZE = 50; // Number of hashes to look up at one time - + static final int DOMAIN_BATCH_SIZE = 500; // Number of domains to look up at one time @Override @@ -256,14 +256,12 @@ public class DiscoveryAttributes { CentralRepository centralRepoDb) throws DiscoveryException { List currentFiles = new ArrayList<>(); Set hashesToLookUp = new HashSet<>(); - List domainsToQuery = new ArrayList<>(); - for (Result result : results) { if (result.getKnown() == TskData.FileKnown.KNOWN) { result.setFrequency(SearchData.Frequency.KNOWN); } - + if (result.getType() != SearchData.Type.DOMAIN) { ResultFile file = (ResultFile) result; if (file.getFrequency() == SearchData.Frequency.UNKNOWN @@ -272,41 +270,41 @@ public class DiscoveryAttributes { hashesToLookUp.add(file.getFirstInstance().getMd5Hash()); currentFiles.add(file); } - - if (hashesToLookUp.size() >= BATCH_SIZE) { - computeFrequency(hashesToLookUp, currentFiles, centralRepoDb); - hashesToLookUp.clear(); - currentFiles.clear(); + if (hashesToLookUp.size() >= BATCH_SIZE) { + computeFrequency(hashesToLookUp, currentFiles, centralRepoDb); + + hashesToLookUp.clear(); + currentFiles.clear(); } } else { ResultDomain domainInstance = (ResultDomain) result; domainsToQuery.add(domainInstance); - + if (domainsToQuery.size() == DOMAIN_BATCH_SIZE) { queryDomainFrequency(domainsToQuery, centralRepoDb); - + domainsToQuery.clear(); } } } - + queryDomainFrequency(domainsToQuery, centralRepoDb); computeFrequency(hashesToLookUp, currentFiles, centralRepoDb); } } - + private static void queryDomainFrequency(List domainsToQuery, CentralRepository centralRepository) throws DiscoveryException { if (domainsToQuery.isEmpty()) { return; } - + try { final Map> resultDomainTable = new HashMap<>(); final StringJoiner joiner = new StringJoiner(", "); final CorrelationAttributeInstance.Type attributeType = centralRepository.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID); - for(ResultDomain domainInstance : domainsToQuery) { + for (ResultDomain domainInstance : domainsToQuery) { try { final String domainValue = domainInstance.getDomain(); final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue); @@ -320,10 +318,10 @@ public class DiscoveryAttributes { } final String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType); - final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency " + - "FROM " + tableName + " " + - "WHERE value IN (" + joiner + ") " + - "GROUP BY value"; + final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency " + + "FROM " + tableName + " " + + "WHERE value IN (" + joiner + ") " + + "GROUP BY value"; final DomainFrequencyCallback frequencyCallback = new DomainFrequencyCallback(resultDomainTable); centralRepository.processSelectClause(domainFrequencyQuery, frequencyCallback); @@ -335,15 +333,15 @@ public class DiscoveryAttributes { throw new DiscoveryException("Fatal exception encountered querying the CR.", ex); } } - + private static class DomainFrequencyCallback implements InstanceTableCallback { - + private final Map> domainLookup; private SQLException sqlCause; - + private DomainFrequencyCallback(Map> domainLookup) { this.domainLookup = domainLookup; - } + } @Override public void process(ResultSet resultSet) { @@ -351,9 +349,9 @@ public class DiscoveryAttributes { while (resultSet.next()) { String domain = resultSet.getString("domain_name"); Long frequency = resultSet.getLong("frequency"); - + List domainInstances = domainLookup.get(domain); - for(ResultDomain domainInstance : domainInstances) { + for (ResultDomain domainInstance : domainInstances) { domainInstance.setFrequency(SearchData.Frequency.fromCount(frequency)); } } @@ -361,7 +359,7 @@ public class DiscoveryAttributes { this.sqlCause = ex; } } - + SQLException getCause() { return this.sqlCause; } @@ -560,6 +558,30 @@ public class DiscoveryAttributes { } } + /** + * Attribute for grouping/sorting by date of most recent activity. + */ + static class MostRecentActivityDateAttribute extends AttributeType { + + @Override + public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) { + return new DiscoveryKeyUtils.MostRecentActivityDateGroupKey(result); + } + + } + + /** + * Attribute for grouping/sorting by date of first activity. + */ + static class FirstActivityDateAttribute extends AttributeType { + + @Override + public DiscoveryKeyUtils.GroupKey getGroupKey(Result result) { + return new DiscoveryKeyUtils.FirstActivityDateGroupKey(result); + } + + } + /** * Attribute for grouping/sorting by objects detected */ @@ -682,6 +704,8 @@ public class DiscoveryAttributes { "DiscoveryAttributes.GroupingAttributeType.interestingItem.displayName=Interesting Item", "DiscoveryAttributes.GroupingAttributeType.tag.displayName=Tag", "DiscoveryAttributes.GroupingAttributeType.object.displayName=Object Detected", + "DiscoveryAttributes.GroupingAttributeType.mostRecentDate.displayName=Most Recent Activity Date", + "DiscoveryAttributes.GroupingAttributeType.firstDate.displayName=First Activity Date", "DiscoveryAttributes.GroupingAttributeType.none.displayName=None"}) public enum GroupingAttributeType { FILE_SIZE(new FileSizeAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_size_displayName()), @@ -693,6 +717,8 @@ public class DiscoveryAttributes { INTERESTING_ITEM_SET(new InterestingItemAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_interestingItem_displayName()), FILE_TAG(new FileTagAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_tag_displayName()), OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_object_displayName()), + MOST_RECENT_DATE(new MostRecentActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_mostRecentDate_displayName()), + FIRST_DATE(new FirstActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_firstDate_displayName()), NO_GROUPING(new NoGroupingAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_none_displayName()); private final AttributeType attributeType; @@ -713,15 +739,24 @@ public class DiscoveryAttributes { } /** - * Get the list of enums that are valid for grouping images. + * Get the list of enums that are valid for grouping files. * - * @return enums that can be used to group images + * @return Enums that can be used to group files. */ - public static List getOptionsForGrouping() { + public static List getOptionsForGroupingForFiles() { return Arrays.asList(FILE_SIZE, FREQUENCY, PARENT_PATH, OBJECT_DETECTED, HASH_LIST_NAME, INTERESTING_ITEM_SET); } + + /** + * Get the list of enums that are valid for grouping files. + * + * @return Enums that can be used to group files. + */ + public static List getOptionsForGroupingForDomains() { + return Arrays.asList(FREQUENCY, MOST_RECENT_DATE, FIRST_DATE); + } } - + /** * Computes the CR frequency of all the given hashes and updates the list of * files. diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java index 0bcbc20b61..7d4bed0011 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DiscoveryKeyUtils.java @@ -18,9 +18,13 @@ */ package org.sleuthkit.autopsy.discovery.search; +import java.text.SimpleDateFormat; import java.util.Collections; +import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; @@ -64,13 +68,13 @@ public class DiscoveryKeyUtils { SearchKey(String userName, List filters, DiscoveryAttributes.AttributeType groupAttributeType, Group.GroupSortingAlgorithm groupSortingType, - ResultsSorter.SortingMethod fileSortingMethod, + ResultsSorter.SortingMethod fileSortingMethod, SleuthkitCase sleuthkitCase, CentralRepository centralRepository) { this.groupAttributeType = groupAttributeType; this.groupSortingType = groupSortingType; this.fileSortingMethod = fileSortingMethod; this.filters = filters; - + StringBuilder searchStringBuilder = new StringBuilder(); searchStringBuilder.append(userName); for (AbstractFilter filter : filters) { @@ -81,15 +85,16 @@ public class DiscoveryKeyUtils { this.sleuthkitCase = sleuthkitCase; this.centralRepository = centralRepository; } - + /** - * Construct a SearchKey without a SleuthkitCase or CentralRepositry instance. + * Construct a SearchKey without a SleuthkitCase or CentralRepositry + * instance. */ SearchKey(String userName, List filters, DiscoveryAttributes.AttributeType groupAttributeType, Group.GroupSortingAlgorithm groupSortingType, ResultsSorter.SortingMethod fileSortingMethod) { - this(userName, filters, groupAttributeType, groupSortingType, + this(userName, filters, groupAttributeType, groupSortingType, fileSortingMethod, null, null); } @@ -109,11 +114,11 @@ public class DiscoveryKeyUtils { } SearchKey otherSearchKey = (SearchKey) otherKey; - if (this.sleuthkitCase != otherSearchKey.getSleuthkitCase() || - this.centralRepository != otherSearchKey.getCentralRepository()) { + if (this.sleuthkitCase != otherSearchKey.getSleuthkitCase() + || this.centralRepository != otherSearchKey.getCentralRepository()) { return false; } - + return getKeyString().equals(otherSearchKey.getKeyString()); } @@ -125,32 +130,54 @@ public class DiscoveryKeyUtils { } /** - * @return the keyString + * Get the String representation of this key. + * + * @return The String representation of this key. */ String getKeyString() { return keyString; } - + + /** + * Get the list of filters associated with this key. + * + * @return The list of filters associated with this key. + */ List getFilters() { return Collections.unmodifiableList(this.filters); } - + + /** + * Get the group sorting type for this key. + * + * @return The group sorting type for this key. + */ Group.GroupSortingAlgorithm getGroupSortingType() { return groupSortingType; } + /** + * Get the grouping attribute for this key. + * + * @return The grouping attribute for this key. + */ DiscoveryAttributes.AttributeType getGroupAttributeType() { return groupAttributeType; } + /** + * Get the fileSorting + * + * @return + */ ResultsSorter.SortingMethod getFileSortingMethod() { return fileSortingMethod; } - + SleuthkitCase getSleuthkitCase() { return this.sleuthkitCase; } - + CentralRepository getCentralRepository() { return this.centralRepository; } @@ -326,12 +353,12 @@ public class DiscoveryKeyUtils { private final String keywordListNamesString; @NbBundle.Messages({ - "FileSearch.KeywordListGroupKey.noKeywords=None"}) + "DiscoveryKeyUtils.KeywordListGroupKey.noKeywords=None"}) KeywordListGroupKey(ResultFile file) { keywordListNames = file.getKeywordListNames(); if (keywordListNames.isEmpty()) { - keywordListNamesString = Bundle.FileSearch_KeywordListGroupKey_noKeywords(); + keywordListNamesString = Bundle.DiscoveryKeyUtils_KeywordListGroupKey_noKeywords(); } else { keywordListNamesString = String.join(",", keywordListNames); // NON-NLS } @@ -760,12 +787,12 @@ public class DiscoveryKeyUtils { private final String hashSetNamesString; @NbBundle.Messages({ - "FileSearch.HashHitsGroupKey.noHashHits=None"}) + "DiscoveryKeyUtils.HashHitsGroupKey.noHashHits=None"}) HashHitsGroupKey(ResultFile file) { hashSetNames = file.getHashSetNames(); if (hashSetNames.isEmpty()) { - hashSetNamesString = Bundle.FileSearch_HashHitsGroupKey_noHashHits(); + hashSetNamesString = Bundle.DiscoveryKeyUtils_HashHitsGroupKey_noHashHits(); } else { hashSetNamesString = String.join(",", hashSetNames); // NON-NLS } @@ -841,12 +868,12 @@ public class DiscoveryKeyUtils { private final String interestingItemSetNamesString; @NbBundle.Messages({ - "FileSearch.InterestingItemGroupKey.noSets=None"}) + "DiscoveryKeyUtils.InterestingItemGroupKey.noSets=None"}) InterestingItemGroupKey(ResultFile file) { interestingItemSetNames = file.getInterestingSetNames(); if (interestingItemSetNames.isEmpty()) { - interestingItemSetNamesString = Bundle.FileSearch_InterestingItemGroupKey_noSets(); + interestingItemSetNamesString = Bundle.DiscoveryKeyUtils_InterestingItemGroupKey_noSets(); } else { interestingItemSetNamesString = String.join(",", interestingItemSetNames); // NON-NLS } @@ -913,6 +940,176 @@ public class DiscoveryKeyUtils { } } + /** + * Key representing a date of most recent activity. + */ + static class MostRecentActivityDateGroupKey extends GroupKey { + + private final Long epochDate; + private final String dateNameString; + + @NbBundle.Messages({ + "DiscoveryKeyUtils.MostRecentActivityDateGroupKey.noDate=No Date Available"}) + MostRecentActivityDateGroupKey(Result result) { + if (result instanceof ResultDomain) { + epochDate = ((ResultDomain) result).getActivityEnd(); + dateNameString = new SimpleDateFormat("yyyy/MM/dd").format(new Date(TimeUnit.SECONDS.toMillis(epochDate))); + } else { + epochDate = Long.MAX_VALUE; + dateNameString = Bundle.DiscoveryKeyUtils_MostRecentActivityDateGroupKey_noDate(); + } + } + + @Override + String getDisplayName() { + return getDateNameString(); + } + + @Override + public boolean equals(Object otherKey) { + if (otherKey == this) { + return true; + } + + if (!(otherKey instanceof MostRecentActivityDateGroupKey)) { + return false; + } + + MostRecentActivityDateGroupKey dateGroupKey = (MostRecentActivityDateGroupKey) otherKey; + return getDateNameString().equals(dateGroupKey.getDateNameString()); + } + + @Override + public int hashCode() { + return Objects.hash(getDateNameString()); + } + + @Override + public int compareTo(GroupKey otherGroupKey) { + if (otherGroupKey instanceof MostRecentActivityDateGroupKey) { + MostRecentActivityDateGroupKey otherDateGroupKey = (MostRecentActivityDateGroupKey) otherGroupKey; + + // Put the empty list at the end + if (this.getEpochDate().equals(Long.MAX_VALUE)) { + if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { + return 0; + } else { + return 1; + } + } else if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { + return -1; + } + + return getDateNameString().compareTo(otherDateGroupKey.getDateNameString()); + } else { + return compareClassNames(otherGroupKey); + } + } + + /** + * Get the date this group is for as a Long. + * + * @return The date. + */ + Long getEpochDate() { + return epochDate; + } + + /** + * Get the name which identifies this group. + * + * @return The dateNameString + */ + String getDateNameString() { + return dateNameString; + } + } + + /** + * Key representing a date of first activity. + */ + static class FirstActivityDateGroupKey extends GroupKey { + + private final Long epochDate; + private final String dateNameString; + + @NbBundle.Messages({ + "DiscoveryKeyUtils.FirstActivityDateGroupKey.noDate=No Date Available"}) + FirstActivityDateGroupKey(Result result) { + if (result instanceof ResultDomain) { + epochDate = ((ResultDomain) result).getActivityStart(); + dateNameString = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date(TimeUnit.SECONDS.toMillis(epochDate))); + } else { + epochDate = Long.MAX_VALUE; + dateNameString = Bundle.DiscoveryKeyUtils_FirstActivityDateGroupKey_noDate(); + } + } + + @Override + String getDisplayName() { + return getDateNameString(); + } + + @Override + public boolean equals(Object otherKey) { + if (otherKey == this) { + return true; + } + + if (!(otherKey instanceof FirstActivityDateGroupKey)) { + return false; + } + + FirstActivityDateGroupKey dateGroupKey = (FirstActivityDateGroupKey) otherKey; + return getDateNameString().equals(dateGroupKey.getDateNameString()); + } + + @Override + public int hashCode() { + return Objects.hash(getDateNameString()); + } + + @Override + public int compareTo(GroupKey otherGroupKey) { + if (otherGroupKey instanceof FirstActivityDateGroupKey) { + FirstActivityDateGroupKey otherDateGroupKey = (FirstActivityDateGroupKey) otherGroupKey; + + // Put the empty list at the end + if (this.getEpochDate().equals(Long.MAX_VALUE)) { + if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { + return 0; + } else { + return 1; + } + } else if (otherDateGroupKey.getEpochDate().equals(Long.MAX_VALUE)) { + return -1; + } + + return getDateNameString().compareTo(otherDateGroupKey.getDateNameString()); + } else { + return compareClassNames(otherGroupKey); + } + } + + /** + * Get the date this group is for as a Long. + * + * @return The date. + */ + Long getEpochDate() { + return epochDate; + } + + /** + * Get the name which identifies this group. + * + * @return The dateNameString + */ + String getDateNameString() { + return dateNameString; + } + } + /** * Key representing an object detected group */ @@ -922,12 +1119,12 @@ public class DiscoveryKeyUtils { private final String objectDetectedNamesString; @NbBundle.Messages({ - "FileSearch.ObjectDetectedGroupKey.noSets=None"}) + "DiscoveryKeyUtils.ObjectDetectedGroupKey.noSets=None"}) ObjectDetectedGroupKey(ResultFile file) { objectDetectedNames = file.getObjectDetectedNames(); if (objectDetectedNames.isEmpty()) { - objectDetectedNamesString = Bundle.FileSearch_ObjectDetectedGroupKey_noSets(); + objectDetectedNamesString = Bundle.DiscoveryKeyUtils_ObjectDetectedGroupKey_noSets(); } else { objectDetectedNamesString = String.join(",", objectDetectedNames); // NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCache.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCache.java index fca63177e5..e7f46821a9 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCache.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchCache.java @@ -36,10 +36,10 @@ import org.sleuthkit.datamodel.SleuthkitCase; class DomainSearchCache { private static final int MAXIMUM_CACHE_SIZE = 10; - private static final LoadingCache>> cache = - CacheBuilder.newBuilder() - .maximumSize(MAXIMUM_CACHE_SIZE) - .build(new DomainSearchCacheLoader()); + private static final LoadingCache>> cache + = CacheBuilder.newBuilder() + .maximumSize(MAXIMUM_CACHE_SIZE) + .build(new DomainSearchCacheLoader()); /** * Get domain search results matching the given parameters. If no results diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/ResultsSorter.java b/Core/src/org/sleuthkit/autopsy/discovery/search/ResultsSorter.java index 2984a517ff..8c5ed6cfb5 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/ResultsSorter.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/ResultsSorter.java @@ -229,16 +229,16 @@ public class ResultsSorter implements Comparator { return compareStrings(((ResultFile) result1).getFirstInstance().getName().toLowerCase(), (((ResultFile) result2).getFirstInstance().getName().toLowerCase())); }; } - + /** * Sorts domain names in lexographical order, ignoring case. */ private static Comparator getDomainNameComparator() { return (Result domain1, Result domain2) -> { - if(domain1.getType() != SearchData.Type.DOMAIN) { + if (domain1.getType() != SearchData.Type.DOMAIN) { return 0; } - + ResultDomain first = (ResultDomain) domain1; ResultDomain second = (ResultDomain) domain2; return compareStrings(first.getDomain().toLowerCase(), second.getDomain().toLowerCase()); @@ -327,7 +327,7 @@ public class ResultsSorter implements Comparator { BY_KEYWORD_LIST_NAMES(Arrays.asList(new DiscoveryAttributes.KeywordListAttribute()), Bundle.FileSorter_SortingMethod_keywordlist_displayName()), // Sort alphabetically by list of keyword list names found BY_FULL_PATH(new ArrayList<>(), - Bundle.FileSorter_SortingMethod_fullPath_displayName()), // Sort alphabetically by path + Bundle.FileSorter_SortingMethod_fullPath_displayName()), // Sort alphabetically by path BY_DOMAIN_NAME(new ArrayList<>(), Bundle.FileSorter_SortingMethod_domain_displayName()); @@ -349,12 +349,22 @@ public class ResultsSorter implements Comparator { } /** - * Get the list of enums that are valid for ordering images. + * Get the list of enums that are valid for ordering files. * - * @return enums that can be used to ordering images. + * @return Enums that can be used to ordering files. */ - public static List getOptionsForOrdering() { + public static List getOptionsForOrderingFiles() { return Arrays.asList(BY_FILE_SIZE, BY_FULL_PATH, BY_FILE_NAME, BY_DATA_SOURCE); } + + /** + * Get the list of enums that are valid for ordering files. + * + * @return Enums that can be used to ordering files. + */ + public static List getOptionsForOrderingDomains() { + return Arrays.asList(BY_DOMAIN_NAME, BY_DATA_SOURCE); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchResults.java b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchResults.java index 3cd64a9e13..f427d97d28 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/SearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/SearchResults.java @@ -76,7 +76,7 @@ class SearchResults { for (Result result : results) { // Add the file to the appropriate group, creating it if necessary GroupKey groupKey = attrType.getGroupKey(result); - + if (!groupMap.containsKey(groupKey)) { groupMap.put(groupKey, new Group(groupSortingType, groupKey)); } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java index 4b4673475c..298789bf3b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/AbstractFiltersPanel.java @@ -32,6 +32,9 @@ import javax.swing.JSplitPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType; +import org.sleuthkit.autopsy.discovery.search.Group; +import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod; import org.sleuthkit.autopsy.discovery.search.SearchData; import org.sleuthkit.autopsy.discovery.search.SearchFiltering; @@ -55,6 +58,9 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li private final JPanel secondColumnPanel = new JPanel(); private int firstColumnY = 0; private int secondColumnY = 0; + private SortingMethod lastSortingMethod = SortingMethod.BY_FILE_NAME; + private GroupingAttributeType lastGroupingAttributeType = GroupingAttributeType.PARENT_PATH; + private Group.GroupSortingAlgorithm lastGroupSortingAlg = Group.GroupSortingAlgorithm.BY_GROUP_SIZE; /** * Setup necessary for implementations of this abstract class. @@ -64,6 +70,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li secondColumnPanel.setLayout(new GridBagLayout()); } + /** * Get the type of results this filters panel is for. * @@ -120,7 +127,7 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li secondColumnY += constraints.gridheight; } } - + /** * Add the panels representing the two columns to the specified JSplitPane. * @@ -248,12 +255,9 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li synchronized List getFilters() { List filtersToUse = new ArrayList<>(); - if (getType().equals(SearchData.Type.DOMAIN)) { - - } else { + if (getType() != SearchData.Type.DOMAIN) { //Domain type does not have a file type filtersToUse.add(new SearchFiltering.FileTypeFilter(getType())); } - for (AbstractDiscoveryFilterPanel filterPanel : filters) { if (filterPanel.getCheckbox().isSelected()) { AbstractFilter filter = filterPanel.getFilter(); @@ -274,4 +278,46 @@ abstract class AbstractFiltersPanel extends JPanel implements ActionListener, Li } } + /** + * @return the lastSortingMethod + */ + SortingMethod getLastSortingMethod() { + return lastSortingMethod; + } + + /** + * @param lastSortingMethod the lastSortingMethod to set + */ + final void setLastSortingMethod(SortingMethod lastSortingMethod) { + this.lastSortingMethod = lastSortingMethod; + } + + /** + * @return the lastGroupingAttributeType + */ + GroupingAttributeType getLastGroupingAttributeType() { + return lastGroupingAttributeType; + } + + /** + * @param lastGroupingAttributeType the lastGroupingAttributeType to set + */ + final void setLastGroupingAttributeType(GroupingAttributeType lastGroupingAttributeType) { + this.lastGroupingAttributeType = lastGroupingAttributeType; + } + + /** + * @return the lastGroupSortingAlg + */ + Group.GroupSortingAlgorithm getLastGroupSortingAlg() { + return lastGroupSortingAlg; + } + + /** + * @param lastGroupSortingAlg the lastGroupSortingAlg to set + */ + final void setLastGroupSortingAlg(Group.GroupSortingAlgorithm lastGroupSortingAlg) { + this.lastGroupSortingAlg = lastGroupSortingAlg; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.form index d269dc7d15..e0d00591cc 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.form @@ -8,6 +8,9 @@ + + + @@ -49,7 +52,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java index eba7fbe1f1..089c18bf13 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ArtifactTypeFilterPanel.java @@ -18,12 +18,16 @@ */ package org.sleuthkit.autopsy.discovery.ui; +import java.util.ArrayList; +import java.util.List; import org.sleuthkit.autopsy.discovery.search.AbstractFilter; import javax.swing.DefaultListModel; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.discovery.search.SearchData; +import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter; import org.sleuthkit.datamodel.BlackboardArtifact; /** @@ -47,7 +51,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { */ private void setUpArtifactTypeFilter() { int count = 0; - DefaultListModel artifactTypeModel = (DefaultListModel) jList1.getModel(); + DefaultListModel artifactTypeModel = (DefaultListModel) artifactList.getModel(); artifactTypeModel.removeAllElements(); for (BlackboardArtifact.ARTIFACT_TYPE artifactType : SearchData.Type.DOMAIN.getArtifactTypes()) { artifactTypeModel.add(count, new ArtifactTypeItem(artifactType)); @@ -66,17 +70,22 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { artifactTypeCheckbox = new javax.swing.JCheckBox(); artifactTypeScrollPane = new javax.swing.JScrollPane(); - jList1 = new javax.swing.JList<>(); + artifactList = new javax.swing.JList<>(); org.openide.awt.Mnemonics.setLocalizedText(artifactTypeCheckbox, org.openide.util.NbBundle.getMessage(ArtifactTypeFilterPanel.class, "ArtifactTypeFilterPanel.artifactTypeCheckbox.text")); // NOI18N + artifactTypeCheckbox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + artifactTypeCheckboxActionPerformed(evt); + } + }); setPreferredSize(new java.awt.Dimension(27, 27)); artifactTypeScrollPane.setPreferredSize(new java.awt.Dimension(27, 27)); - jList1.setModel(new DefaultListModel()); - jList1.setEnabled(false); - artifactTypeScrollPane.setViewportView(jList1); + artifactList.setModel(new DefaultListModel()); + artifactList.setEnabled(false); + artifactTypeScrollPane.setViewportView(artifactList); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -90,9 +99,24 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { ); }// //GEN-END:initComponents + private void artifactTypeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_artifactTypeCheckboxActionPerformed + artifactTypeScrollPane.setEnabled(artifactTypeCheckbox.isSelected()); + artifactList.setEnabled(artifactTypeCheckbox.isSelected()); + }//GEN-LAST:event_artifactTypeCheckboxActionPerformed + @Override void configurePanel(boolean selected, int[] indicesSelected) { artifactTypeCheckbox.setSelected(selected); + if (artifactTypeCheckbox.isEnabled() && artifactTypeCheckbox.isSelected()) { + artifactTypeScrollPane.setEnabled(true); + artifactList.setEnabled(true); + if (indicesSelected != null) { + artifactList.setSelectedIndices(indicesSelected); + } + } else { + artifactTypeScrollPane.setEnabled(false); + artifactList.setEnabled(false); + } } @Override @@ -102,7 +126,7 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { @Override JList getList() { - return null; + return artifactList; } @Override @@ -110,13 +134,24 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @NbBundle.Messages({"ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected."}) @Override String checkForError() { - return "Domain search is not implemented."; + if (artifactTypeCheckbox.isSelected() && artifactList.getSelectedValuesList().isEmpty()) { + return Bundle.ArtifactTypeFilterPanel_selectionNeeded_text(); + } + return ""; } @Override AbstractFilter getFilter() { + if (artifactTypeCheckbox.isSelected() && !artifactList.getSelectedValuesList().isEmpty()) { + List artifactTypeList = new ArrayList<>(); + for (ArtifactTypeItem item : artifactList.getSelectedValuesList()) { + artifactTypeList.add(item.getArtifactType()); + } + return new ArtifactTypeFilter(artifactTypeList); + } return null; } @@ -153,8 +188,8 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel { } // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JList artifactList; private javax.swing.JCheckBox artifactTypeCheckbox; private javax.swing.JScrollPane artifactTypeScrollPane; - private javax.swing.JList jList1; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties index faa0025da5..baffc3ee43 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties @@ -29,17 +29,15 @@ ResultsSplitPaneDivider.showButton.text= ResultsSplitPaneDivider.hideButton.text= ImageFilterPanel.imageFiltersSplitPane.toolTipText= ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show -ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Artifact Type: +ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Result Type: InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item: DocumentPanel.fileSizeLabel.toolTipText= DocumentPanel.isDeletedLabel.toolTipText= DomainFilterPanel.domainFiltersSplitPane.toolTipText= DomainFilterPanel.domainFiltersSplitPane.border.title=Step 2: Filter which domains to show SizeFilterPanel.sizeCheckbox.text=File Size: -DateFilterPanel.dateFilterCheckbox.text=Date Filter: DateFilterPanel.endCheckBox.text=End: DateFilterPanel.startCheckBox.text=Start: -DateFilterPanel.mostRecentButton.text=Only last: DateFilterPanel.daysLabel.text=days of activity ImageThumbnailPanel.isDeletedLabel.toolTipText= ResultsPanel.pageControlsLabel.text=Pages: @@ -54,3 +52,5 @@ PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences: DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show ObjectDetectedFilterPanel.text=Object Detected: DetailsPanel.instancesList.border.title=Instances +DateFilterPanel.mostRecentRadioButton.text=Only last: +DateFilterPanel.dateFilterCheckBox.text=Date Filter: diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED index 1e9224cf9b..df37ba0e3f 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/Bundle.properties-MERGED @@ -83,17 +83,15 @@ ResultsSplitPaneDivider.showButton.text= ResultsSplitPaneDivider.hideButton.text= ImageFilterPanel.imageFiltersSplitPane.toolTipText= ImageFilterPanel.imageFiltersSplitPane.border.title=Step 2: Filter which images to show -ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Artifact Type: +ArtifactTypeFilterPanel.artifactTypeCheckbox.text=Result Type: InterestingItemsFilterPanel.interestingItemsCheckbox.text=Interesting Item: DocumentPanel.fileSizeLabel.toolTipText= DocumentPanel.isDeletedLabel.toolTipText= DomainFilterPanel.domainFiltersSplitPane.toolTipText= DomainFilterPanel.domainFiltersSplitPane.border.title=Step 2: Filter which domains to show SizeFilterPanel.sizeCheckbox.text=File Size: -DateFilterPanel.dateFilterCheckbox.text=Date Filter: DateFilterPanel.endCheckBox.text=End: DateFilterPanel.startCheckBox.text=Start: -DateFilterPanel.mostRecentButton.text=Only last: DateFilterPanel.daysLabel.text=days of activity ImageThumbnailPanel.isDeletedLabel.toolTipText= ResultsPanel.pageControlsLabel.text=Pages: @@ -108,6 +106,8 @@ PastOccurrencesFilterPanel.pastOccurrencesCheckbox.text=Past Occurrences: DocumentFilterPanel.documentsFiltersSplitPane.border.title=Step 2: Filter which documents to show ObjectDetectedFilterPanel.text=Object Detected: DetailsPanel.instancesList.border.title=Instances +DateFilterPanel.mostRecentRadioButton.text=Only last: +DateFilterPanel.dateFilterCheckBox.text=Date Filter: VideoThumbnailPanel.bytes.text=bytes VideoThumbnailPanel.deleted.text=All instances of file are deleted. VideoThumbnailPanel.gigaBytes.text=GB diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.form b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.form index 6801b3f579..8ecca7e27b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.form +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.form @@ -4,12 +4,15 @@ - + - + + + + @@ -51,7 +54,7 @@ - + @@ -76,7 +79,7 @@ - + @@ -100,10 +103,16 @@ + + + + + + @@ -117,18 +126,19 @@ - + + - + - + @@ -144,6 +154,9 @@ + + + @@ -155,6 +168,9 @@ + + + @@ -183,7 +199,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java index cf78c5fe7d..668e441f95 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DateFilterPanel.java @@ -18,13 +18,19 @@ */ package org.sleuthkit.autopsy.discovery.ui; +import java.awt.event.ActionListener; +import java.time.LocalDate; +import java.time.Period; +import java.time.ZoneId; import org.sleuthkit.autopsy.discovery.search.AbstractFilter; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.SpinnerNumberModel; +import javax.swing.event.ListSelectionListener; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.communications.Utils; +import org.sleuthkit.autopsy.discovery.search.SearchFiltering; /** * Filter panel for allowing the user to filter on date. @@ -33,6 +39,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { private static final long serialVersionUID = 1L; private final SpinnerNumberModel numberModel; + private static final long SECS_PER_DAY = 86400; /** * Creates new form DateFilterPanel. @@ -56,31 +63,39 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { private void initComponents() { buttonGroup1 = new javax.swing.ButtonGroup(); - dateFilterCheckbox = new javax.swing.JCheckBox(); + dateFilterCheckBox = new javax.swing.JCheckBox(); jPanel1 = new javax.swing.JPanel(); daysSpinner = new javax.swing.JSpinner(numberModel); daysLabel = new javax.swing.JLabel(); - mostRecentButton = new javax.swing.JRadioButton(); + mostRecentRadioButton = new javax.swing.JRadioButton(); startCheckBox = new javax.swing.JCheckBox(); startDatePicker = new com.github.lgooddatepicker.components.DatePicker(); endDatePicker = new com.github.lgooddatepicker.components.DatePicker(); endCheckBox = new javax.swing.JCheckBox(); rangeRadioButton = new javax.swing.JRadioButton(); - org.openide.awt.Mnemonics.setLocalizedText(dateFilterCheckbox, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.dateFilterCheckbox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(dateFilterCheckBox, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.dateFilterCheckBox.text")); // NOI18N + dateFilterCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dateFilterCheckBoxActionPerformed(evt); + } + }); + daysSpinner.setModel(new javax.swing.SpinnerNumberModel(1, 1, null, 1)); daysSpinner.setEnabled(false); daysSpinner.setPreferredSize(new java.awt.Dimension(75, 26)); + daysSpinner.setValue(7); org.openide.awt.Mnemonics.setLocalizedText(daysLabel, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.daysLabel.text")); // NOI18N daysLabel.setEnabled(false); - buttonGroup1.add(mostRecentButton); - org.openide.awt.Mnemonics.setLocalizedText(mostRecentButton, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.mostRecentButton.text")); // NOI18N - mostRecentButton.setEnabled(false); - mostRecentButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - mostRecentButtonActionPerformed(evt); + buttonGroup1.add(mostRecentRadioButton); + mostRecentRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(mostRecentRadioButton, org.openide.util.NbBundle.getMessage(DateFilterPanel.class, "DateFilterPanel.mostRecentRadioButton.text")); // NOI18N + mostRecentRadioButton.setEnabled(false); + mostRecentRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + mostRecentRadioButtonStateChanged(evt); } }); @@ -92,10 +107,12 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { } }); + startDatePicker.setDate(LocalDate.now()); startDatePicker.setEnabled(false); startDatePicker.setMinimumSize(new java.awt.Dimension(60, 22)); startDatePicker.setPreferredSize(new java.awt.Dimension(110, 22)); + endDatePicker.setDate(LocalDate.now()); endDatePicker.setEnabled(false); endDatePicker.setMinimumSize(new java.awt.Dimension(60, 22)); endDatePicker.setPreferredSize(new java.awt.Dimension(110, 22)); @@ -110,9 +127,9 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { buttonGroup1.add(rangeRadioButton); rangeRadioButton.setEnabled(false); - rangeRadioButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - rangeRadioButtonActionPerformed(evt); + rangeRadioButton.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + rangeRadioButtonStateChanged(evt); } }); @@ -121,7 +138,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(mostRecentButton, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(mostRecentRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -141,7 +158,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(mostRecentButton) + .addComponent(mostRecentRadioButton) .addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(daysLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) @@ -176,31 +193,48 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { }// //GEN-END:initComponents private void startCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_startCheckBoxStateChanged - startDatePicker.setEnabled(startCheckBox.isSelected()); -// validateFilters(); //TODO JIRA-6714 when search will begin doing something + startDatePicker.setEnabled(startCheckBox.isEnabled() && startCheckBox.isSelected()); }//GEN-LAST:event_startCheckBoxStateChanged private void endCheckBoxStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_endCheckBoxStateChanged - endDatePicker.setEnabled(endCheckBox.isSelected()); -// validateFilters(); //TODO JIRA-6714 when search will begin doing something + endDatePicker.setEnabled(endCheckBox.isEnabled() && endCheckBox.isSelected()); }//GEN-LAST:event_endCheckBoxStateChanged - private void mostRecentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mostRecentButtonActionPerformed + private void dateFilterCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateFilterCheckBoxActionPerformed + rangeRadioButton.setEnabled(dateFilterCheckBox.isSelected()); + mostRecentRadioButton.setEnabled(dateFilterCheckBox.isSelected()); + rangeRadioButton.firePropertyChange("DateFilterChange", !rangeRadioButton.isEnabled(), rangeRadioButton.isEnabled()); + mostRecentRadioButton.firePropertyChange("DateFilterChange", !mostRecentRadioButton.isEnabled(), mostRecentRadioButton.isEnabled()); + }//GEN-LAST:event_dateFilterCheckBoxActionPerformed - }//GEN-LAST:event_mostRecentButtonActionPerformed + private void mostRecentRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_mostRecentRadioButtonStateChanged + daysSpinner.setEnabled(mostRecentRadioButton.isSelected()); + daysLabel.setEnabled(mostRecentRadioButton.isSelected()); + }//GEN-LAST:event_mostRecentRadioButtonStateChanged - private void rangeRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rangeRadioButtonActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_rangeRadioButtonActionPerformed + private void rangeRadioButtonStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_rangeRadioButtonStateChanged + startCheckBox.setEnabled(rangeRadioButton.isEnabled() && rangeRadioButton.isSelected()); + endCheckBox.setEnabled(rangeRadioButton.isEnabled() && rangeRadioButton.isSelected()); + startCheckBox.firePropertyChange("StartButtonChange", true, false); + endCheckBox.firePropertyChange("EndButtonChange", true, false); + }//GEN-LAST:event_rangeRadioButtonStateChanged @Override void configurePanel(boolean selected, int[] indicesSelected) { - dateFilterCheckbox.setSelected(selected); + dateFilterCheckBox.setSelected(selected); + if (dateFilterCheckBox.isEnabled() && dateFilterCheckBox.isSelected()) { + mostRecentRadioButton.setEnabled(true); + rangeRadioButton.setEnabled(true); + mostRecentRadioButton.setSelected(true); + } else { + mostRecentRadioButton.setEnabled(false); + rangeRadioButton.setEnabled(false); + } } @Override JCheckBox getCheckbox() { - return dateFilterCheckbox; + return dateFilterCheckBox; } @Override @@ -213,26 +247,81 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel { return null; } + @Override + void addListeners(ActionListener actionListener, ListSelectionListener listListener) { + dateFilterCheckBox.addActionListener(actionListener); + startCheckBox.addActionListener(actionListener); + endCheckBox.addActionListener(actionListener); + rangeRadioButton.addActionListener(actionListener); + mostRecentRadioButton.addActionListener(actionListener); + } + + @Override + void removeListeners() { + for (ActionListener listener : dateFilterCheckBox.getActionListeners()) { + dateFilterCheckBox.removeActionListener(listener); + } + for (ActionListener listener : rangeRadioButton.getActionListeners()) { + rangeRadioButton.removeActionListener(listener); + } + for (ActionListener listener : mostRecentRadioButton.getActionListeners()) { + mostRecentRadioButton.removeActionListener(listener); + } + for (ActionListener listener : rangeRadioButton.getActionListeners()) { + rangeRadioButton.removeActionListener(listener); + } + for (ActionListener listener : startCheckBox.getActionListeners()) { + startCheckBox.removeActionListener(listener); + } + for (ActionListener listener : endCheckBox.getActionListeners()) { + endCheckBox.removeActionListener(listener); + } + } + + @NbBundle.Messages({"DateFilterPanel.invalidRange.text=Range or Only Last must be selected", + "DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter"}) @Override String checkForError() { - return "Domain search is not implemented."; + if (dateFilterCheckBox.isSelected()) { + if (!(rangeRadioButton.isSelected() || mostRecentRadioButton.isSelected())) { + return Bundle.DateFilterPanel_invalidRange_text(); + } else if (rangeRadioButton.isSelected() && !(startCheckBox.isSelected() || endCheckBox.isSelected())) { + return Bundle.DateFilterPanel_startOrEndNeeded_text(); + } + } + return ""; } @Override AbstractFilter getFilter() { + if (dateFilterCheckBox.isSelected()) { + LocalDate startDate = LocalDate.MIN; + LocalDate endDate = LocalDate.MAX; + ZoneId zone = Utils.getUserPreferredZoneId(); + if (rangeRadioButton.isSelected() && (startCheckBox.isSelected() || endCheckBox.isSelected())) { + if (startCheckBox.isSelected() && startDatePicker.getDate() != null) { + startDate = startDatePicker.getDate(); + } + if (endCheckBox.isSelected() && endDatePicker.getDate() != null) { + endDate = endDatePicker.getDate(); + } + } else if (dateFilterCheckBox.isSelected() && mostRecentRadioButton.isSelected()) { + endDate = LocalDate.now(); + startDate = LocalDate.now().minus(Period.ofDays((Integer) daysSpinner.getValue())); + } + return new SearchFiltering.ArtifactDateRangeFilter(startDate.atStartOfDay(zone).toEpochSecond(), endDate.atStartOfDay(zone).toEpochSecond() + SECS_PER_DAY);//to insure end date is inclusive + } return null; } - - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; - private javax.swing.JCheckBox dateFilterCheckbox; + private javax.swing.JCheckBox dateFilterCheckBox; private javax.swing.JLabel daysLabel; private javax.swing.JSpinner daysSpinner; private javax.swing.JCheckBox endCheckBox; private com.github.lgooddatepicker.components.DatePicker endDatePicker; private javax.swing.JPanel jPanel1; - private javax.swing.JRadioButton mostRecentButton; + private javax.swing.JRadioButton mostRecentRadioButton; private javax.swing.JRadioButton rangeRadioButton; private javax.swing.JCheckBox startCheckBox; private com.github.lgooddatepicker.components.DatePicker startDatePicker; diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java index e76e202eb3..9b7525bbd1 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DiscoveryDialog.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.discovery.search.AbstractFilter; import static java.awt.BorderLayout.CENTER; import java.awt.Color; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; @@ -29,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; +import javax.swing.SwingUtilities; import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; @@ -41,12 +44,9 @@ import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; import org.sleuthkit.autopsy.discovery.search.DiscoveryEventUtils; import org.sleuthkit.autopsy.discovery.search.Group; import org.sleuthkit.autopsy.discovery.search.Group.GroupSortingAlgorithm; -import static org.sleuthkit.autopsy.discovery.search.Group.GroupSortingAlgorithm.BY_GROUP_SIZE; import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType; -import static org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes.GroupingAttributeType.PARENT_PATH; import org.sleuthkit.autopsy.discovery.search.ResultsSorter; import org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod; -import static org.sleuthkit.autopsy.discovery.search.ResultsSorter.SortingMethod.BY_FILE_NAME; import org.sleuthkit.autopsy.discovery.search.SearchData; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -111,12 +111,41 @@ final class DiscoveryDialog extends javax.swing.JDialog { } } }; - for (GroupSortingAlgorithm groupSortAlgorithm : GroupSortingAlgorithm.values()) { - groupSortingComboBox.addItem(groupSortAlgorithm); - } updateSearchSettings(); + groupByCombobox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + SwingUtilities.invokeLater(() -> { + getSelectedFilterPanel().setLastGroupingAttributeType(groupByCombobox.getItemAt(groupByCombobox.getSelectedIndex())); + }); + + } + } + }); + orderByCombobox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + SwingUtilities.invokeLater(() -> { + getSelectedFilterPanel().setLastSortingMethod(orderByCombobox.getItemAt(orderByCombobox.getSelectedIndex())); + }); + } + } + }); + groupSortingComboBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + SwingUtilities.invokeLater(() -> { + getSelectedFilterPanel().setLastGroupSortingAlg(groupSortingComboBox.getItemAt(groupSortingComboBox.getSelectedIndex())); + }); + } + } + }); Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, this.new CasePropertyChangeListener()); IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, this.new ModuleChangeListener()); + } /** @@ -140,7 +169,6 @@ final class DiscoveryDialog extends javax.swing.JDialog { add(imageFilterPanel, CENTER); imageFilterPanel.addPropertyChangeListener(listener); updateComboBoxes(); - groupSortingComboBox.setSelectedItem(BY_GROUP_SIZE); pack(); repaint(); } @@ -167,20 +195,54 @@ final class DiscoveryDialog extends javax.swing.JDialog { * Private helper method to perform update of comboboxes update. */ private void updateComboBoxes() { - groupByCombobox.removeAllItems(); // Set up the grouping attributes - for (DiscoveryAttributes.GroupingAttributeType groupingType : DiscoveryAttributes.GroupingAttributeType.getOptionsForGrouping()) { + List groupingAttrs = new ArrayList<>(); + List sortingMethods = new ArrayList<>(); + groupByCombobox.removeAllItems(); + if (type == SearchData.Type.DOMAIN) { + groupingAttrs.addAll(GroupingAttributeType.getOptionsForGroupingForDomains()); + sortingMethods.addAll(SortingMethod.getOptionsForOrderingDomains()); + } else { + groupingAttrs.addAll(GroupingAttributeType.getOptionsForGroupingForFiles()); + sortingMethods.addAll(SortingMethod.getOptionsForOrderingFiles()); + } + for (GroupingAttributeType groupingType : groupingAttrs) { addTypeToGroupByComboBox(groupingType); } - groupByCombobox.setSelectedItem(PARENT_PATH); + groupByCombobox.setSelectedItem(getSelectedFilterPanel().getLastGroupingAttributeType()); orderByCombobox.removeAllItems(); // Set up the file order list - for (ResultsSorter.SortingMethod method : ResultsSorter.SortingMethod.getOptionsForOrdering()) { + for (SortingMethod method : sortingMethods) { if (method != SortingMethod.BY_FREQUENCY || CentralRepository.isEnabled()) { orderByCombobox.addItem(method); } } - orderByCombobox.setSelectedItem(BY_FILE_NAME); + orderByCombobox.setSelectedItem(getSelectedFilterPanel().getLastSortingMethod()); + groupSortingComboBox.removeAllItems(); + for (GroupSortingAlgorithm groupSortAlgorithm : GroupSortingAlgorithm.values()) { + groupSortingComboBox.addItem(groupSortAlgorithm); + } + groupSortingComboBox.setSelectedItem(getSelectedFilterPanel().getLastGroupSortingAlg()); + } + + /** + * Private helper method to get the correct panel for the selected type. + * + * @return The panel that corresponds to the currently selected type. + */ + private AbstractFiltersPanel getSelectedFilterPanel() { + switch (type) { + case IMAGE: + return imageFilterPanel; + case VIDEO: + return videoFilterPanel; + case DOCUMENT: + return documentFilterPanel; + case DOMAIN: + return domainFilterPanel; + default: + return imageFilterPanel; + } } /** @@ -221,29 +283,9 @@ final class DiscoveryDialog extends javax.swing.JDialog { * Validate the filter settings for File type filters. */ synchronized void validateDialog() { - switch (type) { - case IMAGE: - if (imageFilterPanel != null) { - imageFilterPanel.validateFields(); - } - break; - case VIDEO: - if (videoFilterPanel != null) { - videoFilterPanel.validateFields(); - } - break; - case DOCUMENT: - if (documentFilterPanel != null) { - documentFilterPanel.validateFields(); - } - break; - case DOMAIN: - if (domainFilterPanel != null) { - domainFilterPanel.validateFields(); - } - break; - default: - break; + AbstractFiltersPanel panel = getSelectedFilterPanel(); + if (panel != null) { + panel.validateFields(); } } @@ -469,6 +511,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { imagesButton.setForeground(Color.BLACK); type = SearchData.Type.IMAGE; imageFilterPanel.addPropertyChangeListener(listener); + updateComboBoxes(); validateDialog(); pack(); repaint(); @@ -484,6 +527,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { videosButton.setForeground(Color.BLACK); videoFilterPanel.addPropertyChangeListener(listener); type = SearchData.Type.VIDEO; + updateComboBoxes(); validateDialog(); pack(); repaint(); @@ -499,6 +543,7 @@ final class DiscoveryDialog extends javax.swing.JDialog { documentsButton.setForeground(Color.BLACK); type = SearchData.Type.DOCUMENT; documentFilterPanel.addPropertyChangeListener(listener); + updateComboBoxes(); validateDialog(); pack(); repaint(); @@ -512,6 +557,10 @@ final class DiscoveryDialog extends javax.swing.JDialog { remove(imageFilterPanel); imageFilterPanel.removePropertyChangeListener(listener); } + if (domainFilterPanel != null) { + remove(domainFilterPanel); + domainFilterPanel.removePropertyChangeListener(listener); + } if (documentFilterPanel != null) { remove(documentFilterPanel); documentFilterPanel.removePropertyChangeListener(listener); @@ -520,10 +569,6 @@ final class DiscoveryDialog extends javax.swing.JDialog { remove(videoFilterPanel); videoFilterPanel.removePropertyChangeListener(listener); } - if (domainFilterPanel != null) { - remove(domainFilterPanel); - domainFilterPanel.removePropertyChangeListener(listener); - } } private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed @@ -584,7 +629,8 @@ final class DiscoveryDialog extends javax.swing.JDialog { domainsButton.setBackground(SELECTED_COLOR); domainsButton.setForeground(Color.BLACK); type = SearchData.Type.DOMAIN; - documentFilterPanel.addPropertyChangeListener(listener); + domainFilterPanel.addPropertyChangeListener(listener); + updateComboBoxes(); validateDialog(); pack(); repaint(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java index 45ba72338c..cf3209146d 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/DomainFilterPanel.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.discovery.ui; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.discovery.search.DiscoveryAttributes; +import org.sleuthkit.autopsy.discovery.search.ResultsSorter; import org.sleuthkit.autopsy.discovery.search.SearchData; /** @@ -44,6 +46,8 @@ public class DomainFilterPanel extends AbstractFiltersPanel { } addFilter(new PastOccurrencesFilterPanel(TYPE), true, pastOccurrencesIndices, 0); addPanelsToScrollPane(domainFiltersSplitPane); + setLastGroupingAttributeType(DiscoveryAttributes.GroupingAttributeType.MOST_RECENT_DATE); + setLastSortingMethod(ResultsSorter.SortingMethod.BY_DOMAIN_NAME); } /**