mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-05 16:56:16 +00:00
merge from 8183
This commit is contained in:
@@ -72,9 +72,9 @@ DataContentViewerHex.totalPageLabel.text_1=100
|
||||
DataContentViewerHex.pageLabel2.text=Page
|
||||
|
||||
# Product Information panel
|
||||
LBL_Description=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div>
|
||||
LBL_Description=<div style=\"font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif;\">\n <b>Product Version:</b> {0} ({9}) <br><b>Sleuth Kit Version:</b> {7} <br><b>Netbeans RCP Build:</b> {8} <br> <b>Java:</b> {1}; {2}<br> <b>System:</b> {3}; {4}; {5}<br><b>Userdir:</b> {6}</div>
|
||||
Format_OperatingSystem_Value={0} version {1} running on {2}
|
||||
LBL_Copyright=<div style="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools. <br><ul><li>General Information: <a style="color: #1E2A60;" href="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style="color: #1E2A60;" href="https://www.autopsy.com/support/training/">https://www.autopsy.com/support/training/</a></li><li>Support: <a style="color: #1E2A60;" href="https://www.sleuthkit.org/support.php">https://www.sleuthkit.org/support.php</a></li></ul>Copyright © 2003-2020. </div>
|
||||
LBL_Copyright=<div style\="font-size: 12pt; font-family: Verdana, 'Verdana CE', Arial, 'Arial CE', 'Lucida Grande CE', lucida, 'Helvetica CE', sans-serif; ">Autopsy™ is a digital forensics platform based on The Sleuth Kit™ and other tools. <br><ul><li>General Information: <a style\="color: \#1E2A60;" href\="http://www.sleuthkit.org">http://www.sleuthkit.org</a>.</li><li>Training: <a style\="color: \#1E2A60;" href\="https://www.autopsy.com/support/training/">https://www.autopsy.com/support/training/</a></li><li>Support: <a style\="color: \#1E2A60;" href\="https://www.sleuthkit.org/support.php">https://www.sleuthkit.org/support.php</a></li></ul>Copyright © 2003-2020. </div>
|
||||
SortChooser.dialogTitle=Choose Sort Criteria
|
||||
ThumbnailViewChildren.progress.cancelling=(Cancelling)
|
||||
# {0} - file name
|
||||
@@ -97,7 +97,7 @@ DataContentViewerHex.goToPageTextField.text=
|
||||
DataContentViewerHex.goToPageLabel.text=Go to Page:
|
||||
DataResultViewerThumbnail.imagesLabel.text=Images:
|
||||
DataResultViewerThumbnail.imagesRangeLabel.text=-
|
||||
DataResultViewerThumbnail.filePathLabel.text=\
|
||||
DataResultViewerThumbnail.filePathLabel.text=\ \ \
|
||||
AdvancedConfigurationDialog.cancelButton.text=Cancel
|
||||
DataArtifactContentViewer.waitText=Retrieving and preparing data, please wait...
|
||||
DataArtifactContentViewer.errorText=Error retrieving result
|
||||
|
||||
@@ -36,7 +36,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -51,6 +50,7 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEventUtils;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.KeywordHitEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
|
||||
@@ -333,18 +333,6 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
return keywordHitCache.get(searchParams, () -> fetchSetNameHitsForTable(searchParams));
|
||||
}
|
||||
|
||||
public void dropAnalysisResultCache() {
|
||||
analysisResultCache.invalidateAll();
|
||||
}
|
||||
|
||||
public void dropHashHitCache() {
|
||||
setHitCache.invalidateAll();
|
||||
}
|
||||
|
||||
public void dropKeywordHitCache() {
|
||||
keywordHitCache.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a search results dto containing rows of counts data.
|
||||
*
|
||||
@@ -358,10 +346,22 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
*/
|
||||
public TreeResultsDTO<AnalysisResultSearchParam> getAnalysisResultCounts(Long dataSourceId) throws ExecutionException {
|
||||
try {
|
||||
|
||||
Set<BlackboardArtifact.Type> indeterminateTypes = this.treeCounts.getEnqueued().stream()
|
||||
.filter(evt -> dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
|
||||
.map(evt -> evt.getArtifactType())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// get row dto's sorted by display name
|
||||
Map<BlackboardArtifact.Type, Long> typeCounts = getCounts(BlackboardArtifact.Category.ANALYSIS_RESULT, dataSourceId);
|
||||
List<TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam>> treeItemRows = typeCounts.entrySet().stream()
|
||||
.map(entry -> getTreeItem(entry.getKey(), dataSourceId, TreeDisplayCount.getDeterminate(entry.getValue())))
|
||||
.map(entry -> {
|
||||
TreeDisplayCount displayCount = indeterminateTypes.contains(entry.getKey())
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(entry.getValue());
|
||||
|
||||
return getTreeItem(entry.getKey(), dataSourceId, displayCount);
|
||||
})
|
||||
.sorted(Comparator.comparing(countRow -> countRow.getDisplayName()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -375,7 +375,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
|
||||
private TreeItemDTO<AnalysisResultSearchParam> getTreeItem(BlackboardArtifact.Type type, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
BlackboardArtifact.Category.ANALYSIS_RESULT.name(),
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new AnalysisResultSearchParam(type, dataSourceId),
|
||||
type.getTypeID(),
|
||||
type.getDisplayName(),
|
||||
@@ -458,16 +458,29 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
Long dataSourceId,
|
||||
String nullSetName) throws IllegalArgumentException, ExecutionException {
|
||||
|
||||
Set<String> indeterminateSetNames = new HashSet<>();
|
||||
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof AnalysisResultSetEvent
|
||||
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
|
||||
&& evt.getArtifactType().equals(type)) {
|
||||
indeterminateSetNames.add(((AnalysisResultSetEvent) evt).getSetName());
|
||||
}
|
||||
}
|
||||
|
||||
List<TreeItemDTO<AnalysisResultSetSearchParam>> allSets
|
||||
= getSetCountsMap(type, BlackboardAttribute.Type.TSK_SET_NAME, dataSourceId).entrySet().stream()
|
||||
.filter(entry -> nullSetName != null || entry.getKey() != null)
|
||||
.sorted((a, b) -> compareSetStrings(a.getKey(), b.getKey()))
|
||||
.map(entry -> {
|
||||
TreeDisplayCount displayCount = indeterminateSetNames.contains(entry.getKey())
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(entry.getValue());
|
||||
|
||||
return getSetTreeItem(type,
|
||||
dataSourceId,
|
||||
entry.getKey(),
|
||||
entry.getKey() == null ? nullSetName : entry.getKey(),
|
||||
TreeDisplayCount.getDeterminate(entry.getValue()));
|
||||
displayCount);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -478,7 +491,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
Long dataSourceId, String setName, String displayName, TreeDisplayCount displayCount) {
|
||||
|
||||
return new TreeItemDTO<>(
|
||||
type.getTypeName(),
|
||||
AnalysisResultSetSearchParam.getTypeId(),
|
||||
new AnalysisResultSetSearchParam(type, dataSourceId, setName),
|
||||
setName == null ? 0 : setName,
|
||||
displayName,
|
||||
@@ -528,6 +541,18 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
throw new IllegalArgumentException("Expected data source id to be > 0");
|
||||
}
|
||||
|
||||
Set<Pair<String, Integer>> indeterminateSearchTerms = new HashSet<>();
|
||||
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof KeywordHitEvent
|
||||
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
|
||||
&& evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT)
|
||||
&& Objects.equals(((KeywordHitEvent) evt).getSetName(), setName)) {
|
||||
|
||||
KeywordHitEvent keywordEvt = (KeywordHitEvent) evt;
|
||||
indeterminateSearchTerms.add(Pair.of(keywordEvt.getSearchString(), keywordEvt.getSearchType()));
|
||||
}
|
||||
}
|
||||
|
||||
String dataSourceClause = dataSourceId == null
|
||||
? ""
|
||||
: "AND art.data_source_obj_id = ?\n";
|
||||
@@ -599,29 +624,18 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
long count = resultSet.getLong("count");
|
||||
boolean hasChildren = resultSet.getBoolean("has_children");
|
||||
|
||||
String searchTermModified;
|
||||
switch (searchType) {
|
||||
case 0:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
case 1:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
case 2:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
default:
|
||||
logger.log(Level.WARNING, MessageFormat.format("Non-standard search type value: {0}.", searchType));
|
||||
searchTermModified = searchTerm;
|
||||
break;
|
||||
}
|
||||
String searchTermModified = getSearchTermDisplayName(searchTerm, searchType);
|
||||
|
||||
TreeDisplayCount displayCount = indeterminateSearchTerms.contains(Pair.of(searchTerm, searchType))
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(count);
|
||||
|
||||
TreeItemDTO<KeywordSearchTermParams> treeItem = new TreeItemDTO<>(
|
||||
"KEYWORD_SEARCH_TERMS",
|
||||
KeywordSearchTermParams.getTypeId(),
|
||||
new KeywordSearchTermParams(setName, searchTerm, searchType, hasChildren, dataSourceId),
|
||||
searchTermModified,
|
||||
searchTermModified,
|
||||
TreeDisplayCount.getDeterminate(count)
|
||||
displayCount
|
||||
);
|
||||
|
||||
items.add(treeItem);
|
||||
@@ -638,6 +652,34 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UI display name for a search term.
|
||||
*
|
||||
* @param searchTerm The search term.
|
||||
* @param searchType The search type enum value.
|
||||
*
|
||||
* @return The display name.
|
||||
*/
|
||||
public String getSearchTermDisplayName(String searchTerm, int searchType) {
|
||||
String searchTermModified;
|
||||
switch (searchType) {
|
||||
case 0:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_exactMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
case 1:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_substringMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
case 2:
|
||||
searchTermModified = Bundle.AnalysisResultDAO_getKeywordSearchTermCounts_regexMatch(searchTerm == null ? "" : searchTerm);
|
||||
break;
|
||||
default:
|
||||
logger.log(Level.WARNING, MessageFormat.format("Non-standard search type value: {0}.", searchType));
|
||||
searchTermModified = searchTerm;
|
||||
break;
|
||||
}
|
||||
return searchTermModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get counts for string matches of a particular regex/substring search
|
||||
* term.
|
||||
@@ -687,6 +729,23 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
+ "AND res.search_type = ?\n"
|
||||
+ "GROUP BY keyword";
|
||||
|
||||
Set<String> indeterminateMatches = new HashSet<>();
|
||||
for (AnalysisResultEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof KeywordHitEvent
|
||||
&& (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))
|
||||
&& evt.getArtifactType().equals(BlackboardArtifact.Type.TSK_KEYWORD_HIT)) {
|
||||
|
||||
KeywordHitEvent keywordEvt = (KeywordHitEvent) evt;
|
||||
if (Objects.equals(keywordEvt.getSetName(), setName)
|
||||
&& Objects.equals(keywordEvt.getSearchString(), regexStr)
|
||||
&& keywordEvt.getSearchType() == searchType) {
|
||||
|
||||
indeterminateMatches.add(keywordEvt.getMatch());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(query)) {
|
||||
// get artifact types and counts
|
||||
int paramIdx = 0;
|
||||
@@ -708,12 +767,17 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
String keyword = resultSet.getString("keyword");
|
||||
long count = resultSet.getLong("count");
|
||||
|
||||
TreeDisplayCount displayCount = indeterminateMatches.contains(keyword)
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(count);
|
||||
|
||||
items.add(new TreeItemDTO<>(
|
||||
"KEYWORD_MATCH",
|
||||
KeywordMatchParams.getTypeId(),
|
||||
new KeywordMatchParams(setName, regexStr, keyword, searchType, dataSourceId),
|
||||
keyword,
|
||||
keyword == null ? "" : keyword,
|
||||
TreeDisplayCount.getDeterminate(count)));
|
||||
displayCount
|
||||
));
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "An error occurred while fetching results from result set.", ex);
|
||||
@@ -735,7 +799,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
Set<? extends DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
// get a grouping of artifacts mapping the artifact type id to data source id.
|
||||
Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap = new HashMap<>();
|
||||
Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> setMap = new HashMap<>();
|
||||
@@ -768,16 +832,11 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
clearRelevantCacheEntries(analysisResultMap, setMap);
|
||||
SubDAOUtils.invalidateKeys(this.analysisResultCache, ar -> Pair.of(ar.getArtifactType(), ar.getDataSourceId()), analysisResultMap);
|
||||
SubDAOUtils.invalidateKeys(this.setHitCache, ar -> Pair.of(Pair.of(ar.getArtifactType(), ar.getSetName()), ar.getDataSourceId()), setMap);
|
||||
|
||||
List<AnalysisResultEvent> daoEvents = getResultViewEvents(analysisResultMap, setMap);
|
||||
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(arEvt -> getTreeEvent(arEvt, false))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(daoEvents, treeEvents)
|
||||
.flatMap(lst -> lst.stream())
|
||||
.collect(Collectors.toSet());
|
||||
// GVDTODO handle keyword hits
|
||||
return getResultViewEvents(analysisResultMap, setMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -793,7 +852,7 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
*
|
||||
* @return The list of dao events.
|
||||
*/
|
||||
private List<AnalysisResultEvent> getResultViewEvents(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
|
||||
private Set<? extends DAOEvent> getResultViewEvents(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
|
||||
Stream<AnalysisResultEvent> analysisResultEvts = analysisResultMap.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(dsId -> new AnalysisResultEvent(entry.getKey(), dsId)));
|
||||
|
||||
@@ -801,82 +860,50 @@ public class AnalysisResultDAO extends BlackboardArtifactDAO {
|
||||
.flatMap(entry -> entry.getValue().stream().map(dsId -> new AnalysisResultSetEvent(entry.getKey().getRight(), entry.getKey().getLeft(), dsId)));
|
||||
|
||||
// GVDTODO handle keyword hits
|
||||
return Stream.of(analysisResultEvts, analysisResultSetEvts)
|
||||
|
||||
List<AnalysisResultEvent> daoEvents = Stream.of(analysisResultEvts, analysisResultSetEvts)
|
||||
.flatMap(s -> s)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeDisplayCount.INDETERMINATE), false))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(daoEvents, treeEvents)
|
||||
.flatMap(lst -> lst.stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache entries given the provided digests of autopsy events.
|
||||
*
|
||||
* @param analysisResultMap Contains the analysis results that do not use a
|
||||
* set name. A mapping of analysis result type ids
|
||||
* to data sources where the results were created.
|
||||
* @param resultsWithSetMap Contains the anlaysis results that do use a set
|
||||
* name. A mapping of (analysis result type id, set
|
||||
* name) to data sources where results were
|
||||
* created.
|
||||
*/
|
||||
private void clearRelevantCacheEntries(Map<BlackboardArtifact.Type, Set<Long>> analysisResultMap, Map<Pair<BlackboardArtifact.Type, String>, Set<Long>> resultsWithSetMap) {
|
||||
ConcurrentMap<SearchParams<BlackboardArtifactSearchParam>, AnalysisResultTableSearchResultsDTO> arConcurrentMap = this.analysisResultCache.asMap();
|
||||
arConcurrentMap.forEach((k, v) -> {
|
||||
BlackboardArtifactSearchParam searchParam = k.getParamData();
|
||||
Set<Long> dsIds = analysisResultMap.get(searchParam.getArtifactType());
|
||||
if (dsIds != null && (searchParam.getDataSourceId() == null || dsIds.contains(searchParam.getDataSourceId()))) {
|
||||
arConcurrentMap.remove(k);
|
||||
}
|
||||
});
|
||||
|
||||
ConcurrentMap<SearchParams<AnalysisResultSetSearchParam>, AnalysisResultTableSearchResultsDTO> setConcurrentMap = this.setHitCache.asMap();
|
||||
setConcurrentMap.forEach((k, v) -> {
|
||||
AnalysisResultSetSearchParam searchParam = k.getParamData();
|
||||
Set<Long> dsIds = resultsWithSetMap.get(Pair.of(searchParam.getArtifactType(), searchParam.getSetName()));
|
||||
if (dsIds != null && (searchParam.getDataSourceId() == null || dsIds.contains(searchParam.getDataSourceId()))) {
|
||||
arConcurrentMap.remove(k);
|
||||
}
|
||||
});
|
||||
|
||||
// GVDTODO handle clearing cache for keyword search hits
|
||||
// private final Cache<SearchParams<KeywordHitSearchParam>, AnalysisResultTableSearchResultsDTO> keywordHitCache = CacheBuilder.newBuilder().maximumSize(1000).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TreeEvent instance based on the analysis result event and
|
||||
* Creates a TreeItemDTO instance based on the analysis result event and
|
||||
* whether or not this event should trigger a full refresh of counts.
|
||||
*
|
||||
* @param arEvt The analysis result event.
|
||||
* @param shouldRefresh Whether or not this tree event should trigger a full
|
||||
* refresh of counts.
|
||||
* @param arEvt The analysis result event.
|
||||
* @param displayCount The count to display.
|
||||
*
|
||||
* @return The tree event.
|
||||
*/
|
||||
private TreeEvent getTreeEvent(AnalysisResultEvent arEvt, boolean shouldRefresh) {
|
||||
private TreeItemDTO<?> getTreeItem(AnalysisResultEvent arEvt, TreeDisplayCount displayCount) {
|
||||
// GVDTODO handle keyword items when integrated
|
||||
if (arEvt instanceof AnalysisResultSetEvent) {
|
||||
AnalysisResultSetEvent setEvt = (AnalysisResultSetEvent) arEvt;
|
||||
return new TreeEvent(getSetTreeItem(setEvt.getArtifactType(), setEvt.getDataSourceId(),
|
||||
return getSetTreeItem(setEvt.getArtifactType(), setEvt.getDataSourceId(),
|
||||
setEvt.getSetName(), setEvt.getSetName() == null ? "" : setEvt.getSetName(),
|
||||
shouldRefresh ? TreeDisplayCount.UNSPECIFIED : TreeDisplayCount.INDETERMINATE),
|
||||
shouldRefresh);
|
||||
displayCount);
|
||||
} else {
|
||||
return new TreeEvent(getTreeItem(arEvt.getArtifactType(), arEvt.getDataSourceId(),
|
||||
shouldRefresh ? TreeDisplayCount.UNSPECIFIED : TreeDisplayCount.INDETERMINATE),
|
||||
shouldRefresh);
|
||||
return getTreeItem(arEvt.getArtifactType(), arEvt.getDataSourceId(), displayCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> handleIngestComplete() {
|
||||
return this.treeCounts.flushEvents().stream()
|
||||
.map(arEvt -> getTreeEvent(arEvt, true))
|
||||
.collect(Collectors.toSet());
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
return this.treeCounts.getEventTimeouts().stream()
|
||||
.map(arEvt -> getTreeEvent(arEvt, true))
|
||||
.collect(Collectors.toSet());
|
||||
return SubDAOUtils.getRefreshEvents(this.treeCounts, (arEvt, count) -> getTreeItem(arEvt, count));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,8 +24,17 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
* Key for analysis result in order to retrieve data from DAO.
|
||||
*/
|
||||
public class AnalysisResultSearchParam extends BlackboardArtifactSearchParam {
|
||||
|
||||
|
||||
private static final String TYPE_ID = BlackboardArtifact.Category.ANALYSIS_RESULT.name();
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
public AnalysisResultSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) {
|
||||
super(artifactType, dataSourceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,23 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
* Base class for search params for analysis results that filter by set name.
|
||||
*/
|
||||
public class AnalysisResultSetSearchParam extends AnalysisResultSearchParam {
|
||||
|
||||
|
||||
private static final String TYPE_ID = "ANALYSIS_RESULT_SET";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final String setName;
|
||||
|
||||
public AnalysisResultSetSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId, String setName) {
|
||||
super(artifactType, dataSourceId);
|
||||
this.setName = setName;
|
||||
}
|
||||
|
||||
|
||||
public String getSetName() {
|
||||
return setName;
|
||||
}
|
||||
@@ -63,5 +72,4 @@ public class AnalysisResultSetSearchParam extends AnalysisResultSearchParam {
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
* Key for data artifact in order to retrieve data from DAO.
|
||||
*/
|
||||
public class BlackboardArtifactSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "BLACKBOARD_ARTIFACT";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final BlackboardArtifact.Type artifactType;
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ public class CommAccountsDAO extends AbstractDAO {
|
||||
|
||||
private static TreeResultsDTO.TreeItemDTO<CommAccountsSearchParams> createAccountTreeItem(Account.Type accountType, Long dataSourceId, TreeResultsDTO.TreeDisplayCount count) {
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
"ACCOUNTS",
|
||||
CommAccountsSearchParams.getTypeId(),
|
||||
new CommAccountsSearchParams(accountType, dataSourceId),
|
||||
accountType.getTypeName(),
|
||||
accountType.getDisplayName(),
|
||||
@@ -236,7 +236,7 @@ public class CommAccountsDAO extends AbstractDAO {
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return SubDAOUtils.getIngestCompleteEvents(
|
||||
this.accountCounts,
|
||||
(daoEvt) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.UNSPECIFIED)
|
||||
(daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ public class CommAccountsDAO extends AbstractDAO {
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
return SubDAOUtils.getRefreshEvents(
|
||||
this.accountCounts,
|
||||
(daoEvt) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), TreeResultsDTO.TreeDisplayCount.UNSPECIFIED)
|
||||
(daoEvt, count) -> createAccountTreeItem(daoEvt.getAccountType(), daoEvt.getDataSourceId(), count)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,18 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
*/
|
||||
public class CommAccountsSearchParams extends DataArtifactSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "DATA_ARTIFACT_ACCOUNT";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final Account.Type type;
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
public CommAccountsSearchParams(Account.Type type, Long dataSourceId) {
|
||||
super(BlackboardArtifact.Type.TSK_ACCOUNT, dataSourceId);
|
||||
this.type = type;
|
||||
|
||||
@@ -245,7 +245,7 @@ public class DataArtifactDAO extends BlackboardArtifactDAO {
|
||||
|
||||
private TreeItemDTO<DataArtifactSearchParam> createDataArtifactTreeItem(BlackboardArtifact.Type artifactType, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
BlackboardArtifact.Category.DATA_ARTIFACT.name(),
|
||||
DataArtifactSearchParam.getTypeId(),
|
||||
new DataArtifactSearchParam(artifactType, dataSourceId),
|
||||
artifactType.getTypeID(),
|
||||
getDisplayName(artifactType),
|
||||
@@ -255,13 +255,13 @@ public class DataArtifactDAO extends BlackboardArtifactDAO {
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
|
||||
(daoEvt) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.UNSPECIFIED));
|
||||
(daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
return SubDAOUtils.getRefreshEvents(this.treeCounts,
|
||||
(daoEvt) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), TreeDisplayCount.UNSPECIFIED));
|
||||
(daoEvt, count) -> createDataArtifactTreeItem(daoEvt.getArtifactType(), daoEvt.getDataSourceId(), count));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
*/
|
||||
public class DataArtifactSearchParam extends BlackboardArtifactSearchParam {
|
||||
|
||||
private static final String TYPE_ID = BlackboardArtifact.Category.DATA_ARTIFACT.name();
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
public DataArtifactSearchParam(BlackboardArtifact.Type artifactType, Long dataSourceId) {
|
||||
super (artifactType, dataSourceId);
|
||||
}
|
||||
super(artifactType, dataSourceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,16 @@ import java.util.Objects;
|
||||
* Key for content object in order to retrieve data from DAO.
|
||||
*/
|
||||
public class FileSystemContentSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "FILE_SYSTEM_CONTENT";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final Long contentObjectId;
|
||||
|
||||
public FileSystemContentSearchParam(Long contentObjectId) {
|
||||
|
||||
@@ -23,11 +23,10 @@ import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
@@ -58,11 +57,14 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.LayoutFileRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileRowDTO.SlackFileRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.PoolRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemContentEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemHostEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileSystemPersonEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
|
||||
import org.sleuthkit.datamodel.AbstractContent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
@@ -110,8 +112,11 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
Case.Events.HOSTS_REMOVED_FROM_PERSON.toString()
|
||||
);
|
||||
|
||||
private final Cache<SearchParams<?>, BaseSearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
|
||||
private final Cache<SearchParams<?>, BaseSearchResultsDTO> searchParamsCache
|
||||
= CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
|
||||
private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>();
|
||||
|
||||
private static final String FILE_SYSTEM_TYPE_ID = "FILE_SYSTEM";
|
||||
|
||||
private static FileSystemDAO instance = null;
|
||||
@@ -130,7 +135,7 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
|
||||
FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent;
|
||||
|
||||
return contentEvt.getContentObjectId() == null || key.getContentObjectId().equals(contentEvt.getContentObjectId());
|
||||
return contentEvt.getContentObjectId() == null || Objects.equals(key.getContentObjectId(), contentEvt.getContentObjectId());
|
||||
}
|
||||
|
||||
private boolean isSystemHostInvalidating(FileSystemHostSearchParam key, DAOEvent daoEvent) {
|
||||
@@ -315,19 +320,13 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
return searchParamsCache.get(searchParams, () -> fetchHostsForTable(searchParams));
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
}
|
||||
|
||||
private Long getHostFromDs(Content dataSource) {
|
||||
private Host getHostFromDs(Content dataSource) {
|
||||
if (!(dataSource instanceof DataSource)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Host host = ((DataSource) dataSource).getHost();
|
||||
return host == null ? null : host.getHostId();
|
||||
return ((DataSource) dataSource).getHost();
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "There was an error getting the host for data source with id: " + dataSource.getId(), ex);
|
||||
return null;
|
||||
@@ -358,24 +357,16 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> handleIngestComplete() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
// GVDTODO these can probably be rewritten now that it isn't handling a collection of autopsy events
|
||||
Set<Long> affectedPersons = new HashSet<>();
|
||||
Set<Long> affectedHosts = new HashSet<>();
|
||||
Set<Long> affectedParentContent = new HashSet<>();
|
||||
Content affectedContent = null;
|
||||
Content affectedParentContent = null;
|
||||
Host affectedParentHost = null;
|
||||
|
||||
// GVDTODO person parents and parent of persons not handled yet
|
||||
// optional present but null indicates no person parent
|
||||
Optional<Person> affectedParentPerson = Optional.empty();
|
||||
|
||||
boolean refreshAllContent = false;
|
||||
|
||||
Content content = DAOEventUtils.getDerivedFileContentFromFileEvent(evt);
|
||||
@@ -388,132 +379,158 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
if (parentContent == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
if (invalidatesAllFileSystem(parentContent)) {
|
||||
refreshAllContent = true;
|
||||
} else {
|
||||
affectedParentContent.add(parentContent.getId());
|
||||
affectedContent = content;
|
||||
affectedParentContent = parentContent;
|
||||
}
|
||||
} else if (evt instanceof DataSourceAddedEvent) {
|
||||
Long hostId = getHostFromDs(((DataSourceAddedEvent) evt).getDataSource());
|
||||
if (hostId != null) {
|
||||
affectedHosts.add(hostId);
|
||||
}
|
||||
Host host = getHostFromDs(((DataSourceAddedEvent) evt).getDataSource());
|
||||
affectedParentHost = host;
|
||||
|
||||
} else if (evt instanceof DataSourceNameChangedEvent) {
|
||||
Long hostId = getHostFromDs(((DataSourceNameChangedEvent) evt).getDataSource());
|
||||
if (hostId != null) {
|
||||
affectedHosts.add(hostId);
|
||||
}
|
||||
Host host = getHostFromDs(((DataSourceNameChangedEvent) evt).getDataSource());
|
||||
affectedParentHost = host;
|
||||
|
||||
} else if (evt instanceof HostsAddedEvent) {
|
||||
// GVDTODO how best to handle host added?
|
||||
} else if (evt instanceof HostsUpdatedEvent) {
|
||||
// GVDTODO how best to handle host updated?
|
||||
} else if (evt instanceof HostsAddedToPersonEvent) {
|
||||
Person person = ((HostsAddedToPersonEvent) evt).getPerson();
|
||||
affectedPersons.add(person == null ? null : person.getPersonId());
|
||||
affectedParentPerson = Optional.of(person);
|
||||
} else if (evt instanceof HostsRemovedFromPersonEvent) {
|
||||
Person person = ((HostsRemovedFromPersonEvent) evt).getPerson();
|
||||
affectedPersons.add(person == null ? null : person.getPersonId());
|
||||
affectedParentPerson = Optional.of(person);
|
||||
}
|
||||
|
||||
final boolean triggerFullRefresh = refreshAllContent;
|
||||
// if nothing affected, return no events
|
||||
if (!refreshAllContent && affectedContent == null && affectedParentHost == null && !affectedParentPerson.isPresent()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// GVDTODO handling null ids versus the 'No Persons' option
|
||||
ConcurrentMap<SearchParams<?>, BaseSearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
|
||||
concurrentMap.forEach((k, v) -> {
|
||||
Object searchParams = k.getParamData();
|
||||
if (searchParams instanceof FileSystemPersonSearchParam) {
|
||||
FileSystemPersonSearchParam personParam = (FileSystemPersonSearchParam) searchParams;
|
||||
if (affectedPersons.contains(personParam.getPersonObjectId())) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
} else if (searchParams instanceof FileSystemHostSearchParam) {
|
||||
FileSystemHostSearchParam hostParams = (FileSystemHostSearchParam) searchParams;
|
||||
if (affectedHosts.contains(hostParams.getHostObjectId())) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
} else if (searchParams instanceof FileSystemContentSearchParam) {
|
||||
FileSystemContentSearchParam contentParams = (FileSystemContentSearchParam) searchParams;
|
||||
if (triggerFullRefresh
|
||||
|| contentParams.getContentObjectId() == null
|
||||
|| affectedParentContent.contains(contentParams.getContentObjectId())) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
invalidateKeys(affectedParentPerson, affectedParentHost, affectedContent, refreshAllContent);
|
||||
|
||||
return getDAOEvents(affectedParentPerson, affectedParentHost, affectedContent, affectedParentContent, refreshAllContent);
|
||||
}
|
||||
|
||||
private Set<DAOEvent> getDAOEvents(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, Content affectedParentContent, boolean triggerFullRefresh) {
|
||||
List<DAOEvent> daoEvents = new ArrayList<>();
|
||||
|
||||
if (triggerFullRefresh) {
|
||||
daoEvents.add(new FileSystemContentEvent(null, null, null));
|
||||
} else if (affectedContent != null) {
|
||||
Host parentHost = null;
|
||||
|
||||
try {
|
||||
parentHost = (affectedContent instanceof DataSource)
|
||||
? ((DataSource) affectedContent).getHost()
|
||||
: null;
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "An error occurred while fetching content id and host id for content with id of: " + affectedContent.getId(), ex);
|
||||
}
|
||||
|
||||
daoEvents.add(new FileSystemContentEvent(affectedContent, affectedParentContent == null ? null : affectedParentContent.getId(), parentHost));
|
||||
}
|
||||
|
||||
if (affectedHost != null) {
|
||||
daoEvents.add(new FileSystemHostEvent(affectedHost.getHostId()));
|
||||
}
|
||||
|
||||
affectedPerson.ifPresent((person) -> {
|
||||
daoEvents.add(new FileSystemPersonEvent(person == null ? null : person.getPersonId()));
|
||||
});
|
||||
|
||||
Stream<DAOEvent> fileEvts = triggerFullRefresh
|
||||
? Stream.of(new FileSystemContentEvent(null))
|
||||
: affectedParentContent.stream().map(id -> new FileSystemContentEvent(id));
|
||||
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.INDETERMINATE, false))
|
||||
.filter(evt -> evt != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(
|
||||
affectedPersons.stream().map(id -> new FileSystemPersonEvent(id)),
|
||||
affectedHosts.stream().map(id -> new FileSystemHostEvent(id)),
|
||||
fileEvts
|
||||
)
|
||||
.flatMap(s -> s)
|
||||
return Stream.of(daoEvents, treeEvents)
|
||||
.flatMap(lst -> lst.stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
private void invalidateKeys(Optional<Person> affectedPerson, Host affectedHost, Content affectedContent, boolean triggerFullRefresh) {
|
||||
ConcurrentMap<SearchParams<?>, ?> concurrentMap = this.searchParamsCache.asMap();
|
||||
concurrentMap.forEach((k, v) -> {
|
||||
Object searchParams = k.getParamData();
|
||||
boolean shouldInvalidate = false;
|
||||
if (searchParams instanceof FileSystemPersonSearchParam && affectedPerson.isPresent()) {
|
||||
shouldInvalidate = Objects.equals(
|
||||
((FileSystemPersonSearchParam) searchParams).getPersonObjectId(),
|
||||
// to allow for null parent person
|
||||
affectedPerson.flatMap(p -> Optional.ofNullable(p.getPersonId())).orElse(null)
|
||||
);
|
||||
|
||||
} else if (searchParams instanceof FileSystemHostSearchParam && affectedHost != null) {
|
||||
shouldInvalidate = Objects.equals(
|
||||
((FileSystemHostSearchParam) searchParams).getHostObjectId(),
|
||||
affectedHost.getHostId()
|
||||
);
|
||||
|
||||
} else if (searchParams instanceof FileSystemContentSearchParam) {
|
||||
if (triggerFullRefresh) {
|
||||
shouldInvalidate = true;
|
||||
} else if (affectedContent != null) {
|
||||
shouldInvalidate = Objects.equals(
|
||||
((FileSystemContentSearchParam) searchParams).getContentObjectId(),
|
||||
affectedContent.getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInvalidate) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all data sources belonging to a given host.
|
||||
*
|
||||
*
|
||||
* @param host The host.
|
||||
*
|
||||
*
|
||||
* @return Results containing all data sources for the given host.
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<FileSystemContentSearchParam> getDataSourcesForHost(Host host) throws ExecutionException {
|
||||
try {
|
||||
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
|
||||
for (DataSource ds : Case.getCurrentCaseThrows().getSleuthkitCase().getHostManager().getDataSourcesForHost(host)) {
|
||||
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
|
||||
ds.getClass().getSimpleName(),
|
||||
new FileSystemContentSearchParam(ds.getId()),
|
||||
ds,
|
||||
ds.getName(),
|
||||
null
|
||||
));
|
||||
treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN));
|
||||
}
|
||||
return new TreeResultsDTO<>(treeItemRows);
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
throw new ExecutionException("An error occurred while fetching images for host with ID " + host.getHostId(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create results for a single given data source ID (not its children).
|
||||
*
|
||||
*
|
||||
* @param dataSourceObjId The data source object ID.
|
||||
*
|
||||
*
|
||||
* @return Results containing just this data source.
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<FileSystemContentSearchParam> getSingleDataSource(long dataSourceObjId) throws ExecutionException {
|
||||
try {
|
||||
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
|
||||
DataSource ds = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSourceObjId);
|
||||
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
|
||||
ds.getClass().getSimpleName(),
|
||||
new FileSystemContentSearchParam(ds.getId()),
|
||||
ds,
|
||||
ds.getName(),
|
||||
null
|
||||
));
|
||||
|
||||
treeItemRows.add(createDisplayableContentTreeItem(ds, TreeDisplayCount.NOT_SHOWN));
|
||||
return new TreeResultsDTO<>(treeItemRows);
|
||||
} catch (NoCurrentCaseException | TskCoreException | TskDataException ex) {
|
||||
throw new ExecutionException("An error occurred while fetching data source with ID " + dataSourceObjId, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the children that will be displayed in the tree for a given content ID.
|
||||
* Get the children that will be displayed in the tree for a given content
|
||||
* ID.
|
||||
*
|
||||
* @param contentId Object ID of parent content.
|
||||
*
|
||||
@@ -523,23 +540,18 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
*/
|
||||
public TreeResultsDTO<FileSystemContentSearchParam> getDisplayableContentChildren(Long contentId) throws ExecutionException {
|
||||
try {
|
||||
|
||||
|
||||
List<Content> treeChildren = FileSystemColumnUtils.getVisibleTreeNodeChildren(contentId);
|
||||
|
||||
|
||||
List<TreeResultsDTO.TreeItemDTO<FileSystemContentSearchParam>> treeItemRows = new ArrayList<>();
|
||||
for (Content child : treeChildren) {
|
||||
Long countForNode = null;
|
||||
if ((child instanceof AbstractFile)
|
||||
&& ! (child instanceof LocalFilesDataSource)) {
|
||||
&& !(child instanceof LocalFilesDataSource)) {
|
||||
countForNode = getContentForTable(new FileSystemContentSearchParam(child.getId()), 0, null).getTotalResultsCount();
|
||||
}
|
||||
treeItemRows.add(new TreeResultsDTO.TreeItemDTO<>(
|
||||
child.getClass().getSimpleName(),
|
||||
new FileSystemContentSearchParam(child.getId()),
|
||||
child,
|
||||
getNameForContent(child),
|
||||
countForNode == null ? TreeDisplayCount.NOT_SHOWN : TreeDisplayCount.getDeterminate(countForNode)
|
||||
));
|
||||
TreeDisplayCount displayCount = countForNode == null ? TreeDisplayCount.NOT_SHOWN : TreeDisplayCount.getDeterminate(countForNode);
|
||||
treeItemRows.add(createDisplayableContentTreeItem(child, displayCount));
|
||||
}
|
||||
return new TreeResultsDTO<>(treeItemRows);
|
||||
|
||||
@@ -547,23 +559,160 @@ public class FileSystemDAO extends AbstractDAO {
|
||||
throw new ExecutionException("An error occurred while fetching data artifact counts.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private FileSystemTreeItem createDisplayableContentTreeItem(Content child, TreeDisplayCount displayCount) {
|
||||
return new FileSystemTreeItem(
|
||||
FileSystemContentSearchParam.getTypeId(),
|
||||
new FileSystemContentSearchParam(child == null ? null : child.getId()),
|
||||
child,
|
||||
child == null ? null : getNameForContent(child),
|
||||
child instanceof AbstractFile ? ((AbstractFile) child).getMetaType() : null,
|
||||
displayCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display name for the given content.
|
||||
*
|
||||
*
|
||||
* @param content The content.
|
||||
*
|
||||
*
|
||||
* @return Display name for the content.
|
||||
*/
|
||||
private String getNameForContent(Content content) {
|
||||
if (content instanceof Volume) {
|
||||
return FileSystemColumnUtils.getVolumeDisplayName((Volume)content);
|
||||
return FileSystemColumnUtils.getVolumeDisplayName((Volume) content);
|
||||
} else if (content instanceof AbstractFile) {
|
||||
return FileSystemColumnUtils.convertDotDirName((AbstractFile) content);
|
||||
}
|
||||
return content.getName();
|
||||
}
|
||||
|
||||
private TreeEvent createTreeEvent(DAOEvent daoEvent, TreeDisplayCount count, boolean fullRefresh) {
|
||||
|
||||
if (daoEvent instanceof FileSystemContentEvent) {
|
||||
FileSystemContentEvent contentEvt = (FileSystemContentEvent) daoEvent;
|
||||
|
||||
return new FileSystemTreeEvent(
|
||||
contentEvt.getParentObjId(),
|
||||
contentEvt.getParentHost(),
|
||||
createDisplayableContentTreeItem(contentEvt.getContent(), count),
|
||||
fullRefresh);
|
||||
|
||||
} else if (daoEvent instanceof FileSystemHostEvent) {
|
||||
// GVDTODO not currently integrated into tree
|
||||
} else if (daoEvent instanceof FileSystemPersonEvent) {
|
||||
// GVDTODO not currently integrated into tree
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
handleIngestComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return treeCounts.flushEvents().stream()
|
||||
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true))
|
||||
.filter(evt -> evt != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
return treeCounts.getEventTimeouts().stream()
|
||||
.map(daoEvt -> createTreeEvent(daoEvt, TreeDisplayCount.UNSPECIFIED, true))
|
||||
.filter(evt -> evt != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static class DataSourceRefreshTreeEvent extends TreeEvent {
|
||||
|
||||
public DataSourceRefreshTreeEvent(boolean refresh) {
|
||||
super(null, refresh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class FileSystemTreeItem extends TreeItemDTO<FileSystemContentSearchParam> {
|
||||
|
||||
private final TskData.TSK_FS_META_TYPE_ENUM metaType;
|
||||
|
||||
FileSystemTreeItem(
|
||||
String typeId,
|
||||
FileSystemContentSearchParam searchParams,
|
||||
Object id,
|
||||
String displayName,
|
||||
TskData.TSK_FS_META_TYPE_ENUM metaType,
|
||||
TreeDisplayCount count) {
|
||||
|
||||
super(typeId, searchParams, id, displayName, count);
|
||||
this.metaType = metaType;
|
||||
}
|
||||
|
||||
public TskData.TSK_FS_META_TYPE_ENUM getMetaType() {
|
||||
return metaType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class FileSystemTreeEvent extends TreeEvent {
|
||||
|
||||
private final Long parentContentId;
|
||||
private final Host parentHost;
|
||||
private final FileSystemTreeItem itemRecord;
|
||||
|
||||
FileSystemTreeEvent(Long parentContentId, Host parentHost, FileSystemTreeItem itemRecord, boolean refreshRequired) {
|
||||
super(itemRecord, refreshRequired);
|
||||
this.parentContentId = parentContentId;
|
||||
this.parentHost = parentHost;
|
||||
this.itemRecord = itemRecord;
|
||||
}
|
||||
|
||||
public Long getParentContentId() {
|
||||
return parentContentId;
|
||||
}
|
||||
|
||||
public Host getParentHost() {
|
||||
return parentHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemTreeItem getItemRecord() {
|
||||
// override to be typed and contain extra information
|
||||
return itemRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 31 * hash + Objects.hashCode(this.itemRecord);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final FileSystemTreeEvent other = (FileSystemTreeEvent) obj;
|
||||
if (!Objects.equals(this.itemRecord, other.itemRecord)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for file types by mime type.
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,16 @@ import java.util.Objects;
|
||||
* Key for content object in order to retrieve data from DAO.
|
||||
*/
|
||||
public class FileSystemHostSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "FILE_SYSTEM_HOST";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final Long hostObjectId;
|
||||
|
||||
public FileSystemHostSearchParam(Long hostObjectId) {
|
||||
@@ -59,4 +69,3 @@ public class FileSystemHostSearchParam {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,23 @@ import java.util.Objects;
|
||||
* Key for person object in order to retrieve data from DAO.
|
||||
*/
|
||||
public class FileSystemPersonSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "FILE_SYSTEM_PERSON";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final Long personObjectId;
|
||||
|
||||
/**
|
||||
* Create search param.
|
||||
*
|
||||
* @param personObjectId May be null to fetch hosts not associated with a Person
|
||||
*
|
||||
* @param personObjectId May be null to fetch hosts not associated with a
|
||||
* Person
|
||||
*/
|
||||
public FileSystemPersonSearchParam(Long personObjectId) {
|
||||
this.personObjectId = personObjectId;
|
||||
|
||||
@@ -25,6 +25,15 @@ import java.util.Objects;
|
||||
*/
|
||||
public class FileTypeExtensionsSearchParams {
|
||||
|
||||
private static final String TYPE_ID = "FILE_VIEWS_EXTENSION";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final FileExtSearchFilter filter;
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
@@ -25,9 +25,18 @@ import java.util.Objects;
|
||||
*/
|
||||
public class FileTypeMimeSearchParams {
|
||||
|
||||
private static final String TYPE_ID = "FILE_VIEWS_MIME";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final String mimeType;
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
public FileTypeMimeSearchParams(String mimeType, Long dataSourceId) {
|
||||
this.mimeType = mimeType;
|
||||
this.dataSourceId = dataSourceId;
|
||||
@@ -70,5 +79,4 @@ public class FileTypeMimeSearchParams {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@ import java.util.Objects;
|
||||
*/
|
||||
public class FileTypeSizeSearchParams {
|
||||
|
||||
private static final String TYPE_ID = "FILE_VIEWS_SIZE";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final FileSizeFilter sizeFilter;
|
||||
private final Long dataSourceId;
|
||||
|
||||
@@ -24,7 +24,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
* Key for keyword hits in order to retrieve data from DAO.
|
||||
*/
|
||||
public class HashHitSearchParam extends AnalysisResultSetSearchParam {
|
||||
|
||||
|
||||
private static final String TYPE_ID = "HASH_HIT";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
public HashHitSearchParam(Long dataSourceId, String setName) {
|
||||
super(BlackboardArtifact.Type.TSK_HASHSET_HIT, dataSourceId, setName);
|
||||
}
|
||||
|
||||
@@ -26,19 +26,28 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
*/
|
||||
public class KeywordHitSearchParam extends AnalysisResultSetSearchParam {
|
||||
|
||||
private static final String TYPE_ID = "KEYWORD_HIT";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final String keyword;
|
||||
private final String regex;
|
||||
|
||||
|
||||
public KeywordHitSearchParam(Long dataSourceId, String setName, String keyword, String regex) {
|
||||
super(BlackboardArtifact.Type.TSK_KEYWORD_HIT, dataSourceId, setName);
|
||||
this.keyword = keyword;
|
||||
this.regex = regex;
|
||||
}
|
||||
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
}
|
||||
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
@@ -72,6 +81,5 @@ public class KeywordHitSearchParam extends AnalysisResultSetSearchParam {
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,15 @@ package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
*/
|
||||
public class KeywordMatchParams {
|
||||
|
||||
private static final String TYPE_ID = "KEYWORD_MATCH";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final String setName;
|
||||
private final String searchTerm;
|
||||
private final String keywordMatch;
|
||||
|
||||
@@ -23,6 +23,15 @@ package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
*/
|
||||
public class KeywordSearchTermParams {
|
||||
|
||||
private static final String TYPE_ID = "KEYWORD_SEARCH_TERM";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final String setName;
|
||||
private final String searchTerm;
|
||||
private final boolean hasChildren;
|
||||
|
||||
@@ -259,6 +259,7 @@ public class MainDAO extends AbstractDAO {
|
||||
return allDAOs.stream()
|
||||
.map(subDAO -> subDAO.processEvent(evt))
|
||||
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
|
||||
.filter(e -> e != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -267,6 +268,7 @@ public class MainDAO extends AbstractDAO {
|
||||
return allDAOs.stream()
|
||||
.map((subDAO) -> subDAO.shouldRefreshTree())
|
||||
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
|
||||
.filter(e -> e != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -280,6 +282,7 @@ public class MainDAO extends AbstractDAO {
|
||||
|
||||
return daoStreamEvts.stream()
|
||||
.flatMap(evts -> evts == null ? Stream.empty() : evts.stream())
|
||||
.filter(evt -> evt != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.google.common.cache.CacheBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -187,13 +186,11 @@ public class OsAccountsDAO extends AbstractDAO {
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> handleIngestComplete() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -202,9 +199,9 @@ public class OsAccountsDAO extends AbstractDAO {
|
||||
if (!OS_EVENTS.contains(evt.getPropertyName())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
this.searchParamsCache.invalidateAll();
|
||||
|
||||
|
||||
this.searchParamsCache.invalidateAll();
|
||||
|
||||
return Collections.singleton(new OsAccountEvent());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,17 @@ import java.util.Objects;
|
||||
*/
|
||||
public class OsAccountsSearchParams {
|
||||
|
||||
private static final String TYPE_ID = "OS_ACCOUNTS";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
public OsAccountsSearchParams(Long dataSourceId) {
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
|
||||
@@ -46,32 +48,28 @@ public class SubDAOUtils {
|
||||
* no data source filtering).
|
||||
* @param itemDataSourceMapping The event digest.
|
||||
*/
|
||||
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, Map<T, Set<Long>> itemDataSourceMapping) {
|
||||
invalidateKeys(cache, getKeys, Collections.singletonList(itemDataSourceMapping));
|
||||
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, Map<T, Set<Long>> itemDsMapping) {
|
||||
invalidateKeys(cache, (keyParams) -> {
|
||||
Pair<T, Long> pairItems = getKeys.apply(keyParams);
|
||||
T searchParamsKey = pairItems.getLeft();
|
||||
Long searchParamsDsId = pairItems.getRight();
|
||||
Set<Long> dsIds = itemDsMapping.get(searchParamsKey);
|
||||
return (dsIds != null && (searchParamsDsId == null || dsIds.contains(searchParamsDsId)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Using a digest of event information, clears keys in a cache that may be
|
||||
* effected by events.
|
||||
* Determines what keys should be kept in the cache while iterating through
|
||||
* all the keys.
|
||||
*
|
||||
* @param cache The cache.
|
||||
* @param getKeys Using a key from a cache, provides a tuple
|
||||
* of the relevant key in the data source
|
||||
* mapping and the data source id (or null if
|
||||
* no data source filtering).
|
||||
* @param itemDataSourceMapping The list of event digests.
|
||||
* @param cache The cache.
|
||||
* @param shouldInvalidate If the key should be removed from the cache.
|
||||
*/
|
||||
static <T, K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Function<K, Pair<T, Long>> getKeys, List<Map<T, Set<Long>>> itemDataSourceMapping) {
|
||||
static <K> void invalidateKeys(Cache<SearchParams<K>, ?> cache, Predicate<K> shouldInvalidate) {
|
||||
ConcurrentMap<SearchParams<K>, ?> concurrentMap = cache.asMap();
|
||||
concurrentMap.forEach((k, v) -> {
|
||||
Pair<T, Long> pairItems = getKeys.apply(k.getParamData());
|
||||
T searchParamsKey = pairItems.getLeft();
|
||||
Long searchParamsDsId = pairItems.getRight();
|
||||
for (Map<T, Set<Long>> itemDsMapping : itemDataSourceMapping) {
|
||||
Set<Long> dsIds = itemDsMapping.get(searchParamsKey);
|
||||
if (dsIds != null && (searchParamsDsId == null || dsIds.contains(searchParamsDsId))) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
if (shouldInvalidate.test(k.getParamData())) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -86,9 +84,9 @@ public class SubDAOUtils {
|
||||
*
|
||||
* @return The generated tree events.
|
||||
*/
|
||||
static <E, T> Set<TreeEvent> getIngestCompleteEvents(TreeCounts<E> treeCounts, Function<E, TreeResultsDTO.TreeItemDTO<T>> converter) {
|
||||
static <E, T> Set<TreeEvent> getIngestCompleteEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) {
|
||||
return treeCounts.flushEvents().stream()
|
||||
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt), true))
|
||||
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -102,9 +100,9 @@ public class SubDAOUtils {
|
||||
*
|
||||
* @return The generated tree events.
|
||||
*/
|
||||
static <E, T> Set<TreeEvent> getRefreshEvents(TreeCounts<E> treeCounts, Function<E, TreeResultsDTO.TreeItemDTO<T>> converter) {
|
||||
static <E, T> Set<TreeEvent> getRefreshEvents(TreeCounts<E> treeCounts, BiFunction<E, TreeDisplayCount, TreeItemDTO<T>> converter) {
|
||||
return treeCounts.getEventTimeouts().stream()
|
||||
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt), true))
|
||||
.map(daoEvt -> new TreeEvent(converter.apply(daoEvt, TreeDisplayCount.UNSPECIFIED), true))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,20 +27,14 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@@ -52,7 +46,9 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TagsEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
@@ -90,7 +86,6 @@ public class TagsDAO extends AbstractDAO {
|
||||
private static final int CACHE_SIZE = 5; // rule of thumb: 5 entries times number of cached SearchParams sub-types
|
||||
private static final long CACHE_DURATION = 2;
|
||||
private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES;
|
||||
private final Cache<SearchParams<TagsSearchParams>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
|
||||
private static final String USER_NAME_PROPERTY = "user.name"; //NON-NLS
|
||||
|
||||
@@ -129,6 +124,11 @@ public class TagsDAO extends AbstractDAO {
|
||||
return new ColumnKey(name, name, Bundle.TagsDAO_fileColumns_noDescription());
|
||||
}
|
||||
|
||||
private final Cache<SearchParams<TagsSearchParams>, SearchResultsDTO> searchParamsCache
|
||||
= CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
|
||||
private final TreeCounts<TagsEvent> treeCounts = new TreeCounts<>();
|
||||
|
||||
public SearchResultsDTO getTags(TagsSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
|
||||
if (key.getTagName() == null) {
|
||||
throw new IllegalArgumentException("Must have non-null tag name");
|
||||
@@ -301,71 +301,59 @@ public class TagsDAO extends AbstractDAO {
|
||||
}
|
||||
|
||||
TagsEvent tagEvt = (TagsEvent) daoEvt;
|
||||
return (tagParams.getTagName().getId() == tagEvt.getTagNameId()
|
||||
return (Objects.equals(tagParams.getTagName(), tagEvt.getTagName())
|
||||
&& tagParams.getTagType().equals(tagEvt.getTagType())
|
||||
&& (tagParams.getDataSourceId() == null
|
||||
|| tagEvt.getDataSourceId() == null
|
||||
|| tagParams.getDataSourceId() == tagEvt.getDataSourceId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
private TreeItemDTO<TagsSearchParams> getTreeItem(TagsEvent evt, TreeResultsDTO.TreeDisplayCount count) {
|
||||
return new TreeItemDTO<>(
|
||||
TagsSearchParams.getTypeId(),
|
||||
new TagsSearchParams(evt.getTagName(), evt.getTagType(), evt.getDataSourceId()),
|
||||
evt.getTagName().getId(),
|
||||
evt.getTagName().getDisplayName(),
|
||||
count);
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> handleIngestComplete() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
handleIngestComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
return SubDAOUtils.getRefreshEvents(this.treeCounts, (evt, count) -> getTreeItem(evt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
// GVDTODO this may be rewritten simpler now that it isn't processing a list of events
|
||||
Map<Pair<TagType, Long>, Set<Optional<Long>>> mapping = new HashMap<>();
|
||||
|
||||
// tag type, tag name id, data source id (or null if unknown)
|
||||
Triple<TagType, Long, Long> data = getTagData(evt);
|
||||
if (data != null) {
|
||||
mapping.computeIfAbsent(Pair.of(data.getLeft(), data.getMiddle()), k -> new HashSet<>())
|
||||
.add(Optional.ofNullable(data.getRight()));
|
||||
}
|
||||
|
||||
|
||||
// don't continue if no mapping entries
|
||||
if (mapping.isEmpty()) {
|
||||
TagsEvent data = getTagData(evt);
|
||||
if (data == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
ConcurrentMap<SearchParams<TagsSearchParams>, SearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
|
||||
concurrentMap.forEach((k, v) -> {
|
||||
TagsSearchParams paramData = k.getParamData();
|
||||
Set<Optional<Long>> affectedDataSources = mapping.get(Pair.of(paramData.getTagType(), paramData.getTagName().getId()));
|
||||
// we only clear key if the tag name / type line up and either the parameters data source wasn't specified,
|
||||
// there is a wild card data source for the event, or the data source is contained in the list of data sources
|
||||
// affected by the event
|
||||
if (affectedDataSources != null
|
||||
&& (paramData.getDataSourceId() == null
|
||||
|| affectedDataSources.contains(Optional.empty())
|
||||
|| affectedDataSources.contains(Optional.of(paramData.getDataSourceId())))) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache, (searchParams) -> {
|
||||
return (Objects.equals(searchParams.getTagType(), data.getTagType())
|
||||
&& Objects.equals(searchParams.getTagName(), data.getTagName())
|
||||
&& (searchParams.getDataSourceId() == null || Objects.equals(searchParams.getDataSourceId(), data.getDataSourceId())));
|
||||
});
|
||||
|
||||
return mapping.entrySet().stream()
|
||||
.flatMap(entry -> {
|
||||
TagType tagType = entry.getKey().getLeft();
|
||||
Long tagNameId = entry.getKey().getRight();
|
||||
Collection<TagsEvent> daoEvents = Collections.singletonList(data);
|
||||
|
||||
return entry.getValue().stream()
|
||||
.map((dsIdOpt) -> new TagsEvent(tagType, tagNameId, dsIdOpt.orElse(null)));
|
||||
})
|
||||
Collection<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(arEvt -> new TreeEvent(getTreeItem(arEvt, TreeResultsDTO.TreeDisplayCount.INDETERMINATE), false))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(daoEvents, treeEvents)
|
||||
.flatMap(lst -> lst.stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@@ -378,21 +366,21 @@ public class TagsDAO extends AbstractDAO {
|
||||
* @return tag type, tag name id, data source id (or null if none determined
|
||||
* from event).
|
||||
*/
|
||||
private Triple<TagType, Long, Long> getTagData(PropertyChangeEvent evt) {
|
||||
private TagsEvent getTagData(PropertyChangeEvent evt) {
|
||||
if (evt instanceof BlackBoardArtifactTagAddedEvent) {
|
||||
BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt;
|
||||
// ensure tag added event has a valid content id
|
||||
if (event.getAddedTag() != null
|
||||
&& event.getAddedTag().getContent() != null
|
||||
&& event.getAddedTag().getArtifact() != null) {
|
||||
return Triple.of(TagType.RESULT, event.getAddedTag().getName().getId(), event.getAddedTag().getArtifact().getDataSourceObjectID());
|
||||
return new TagsEvent(TagType.RESULT, event.getAddedTag().getName(), event.getAddedTag().getArtifact().getDataSourceObjectID());
|
||||
}
|
||||
|
||||
} else if (evt instanceof BlackBoardArtifactTagDeletedEvent) {
|
||||
BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt;
|
||||
BlackBoardArtifactTagDeletedEvent.DeletedBlackboardArtifactTagInfo deletedTagInfo = event.getDeletedTagInfo();
|
||||
if (deletedTagInfo != null) {
|
||||
return Triple.of(TagType.RESULT, deletedTagInfo.getName().getId(), null);
|
||||
return new TagsEvent(TagType.RESULT, deletedTagInfo.getName(), null);
|
||||
}
|
||||
} else if (evt instanceof ContentTagAddedEvent) {
|
||||
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
|
||||
@@ -400,14 +388,14 @@ public class TagsDAO extends AbstractDAO {
|
||||
if (event.getAddedTag() != null && event.getAddedTag().getContent() != null) {
|
||||
Content content = event.getAddedTag().getContent();
|
||||
Long dsId = content instanceof AbstractFile ? ((AbstractFile) content).getDataSourceObjectId() : null;
|
||||
return Triple.of(TagType.FILE, event.getAddedTag().getName().getId(), dsId);
|
||||
return new TagsEvent(TagType.FILE, event.getAddedTag().getName(), dsId);
|
||||
}
|
||||
} else if (evt instanceof ContentTagDeletedEvent) {
|
||||
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
|
||||
// ensure tag deleted event has a valid content id
|
||||
ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = event.getDeletedTagInfo();
|
||||
if (deletedTagInfo != null) {
|
||||
return Triple.of(TagType.FILE, deletedTagInfo.getName().getId(), null);
|
||||
return new TagsEvent(TagType.FILE, deletedTagInfo.getName(), null);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -25,7 +25,16 @@ import org.sleuthkit.datamodel.TagName;
|
||||
* Key for accessing data about tags from the DAO.
|
||||
*/
|
||||
public class TagsSearchParams {
|
||||
|
||||
|
||||
private static final String TYPE_ID = "TAG";
|
||||
|
||||
/**
|
||||
* @return The type id for this search parameter.
|
||||
*/
|
||||
public static String getTypeId() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
public enum TagType {
|
||||
FILE,
|
||||
RESULT;
|
||||
@@ -34,7 +43,7 @@ public class TagsSearchParams {
|
||||
private final TagType type;
|
||||
private final TagName tagName;
|
||||
private final Long dataSourceId;
|
||||
|
||||
|
||||
public TagsSearchParams(TagName tagName, TagType type, Long dataSourceId) {
|
||||
this.tagName = tagName;
|
||||
this.type = type;
|
||||
|
||||
@@ -87,6 +87,7 @@ public class TreeResultsDTO<T> {
|
||||
case INDETERMINATE:
|
||||
return "...";
|
||||
case NOT_SHOWN:
|
||||
case UNSPECIFIED:
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.sql.SQLException;
|
||||
import java.text.MessageFormat;
|
||||
@@ -34,9 +35,9 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -55,6 +56,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DeletedContentEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeExtensionsEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeMimeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.FileTypeSizeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeCounts;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.DAOFetcher;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
@@ -62,6 +64,7 @@ import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_TYPE_ENUM;
|
||||
|
||||
/**
|
||||
* Provides information to populate the results viewer for data in the views
|
||||
@@ -74,7 +77,14 @@ public class ViewsDAO extends AbstractDAO {
|
||||
private static final int CACHE_SIZE = 15; // rule of thumb: 5 entries times number of cached SearchParams sub-types
|
||||
private static final long CACHE_DURATION = 2;
|
||||
private static final TimeUnit CACHE_DURATION_UNITS = TimeUnit.MINUTES;
|
||||
private final Cache<SearchParams<?>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
private static final Map<String, Set<FileExtSearchFilter>> EXTENSION_FILTER_MAP
|
||||
= Stream.of((FileExtSearchFilter[]) FileExtRootFilter.values(), FileExtDocumentFilter.values(), FileExtExecutableFilter.values())
|
||||
.flatMap(arr -> Stream.of(arr))
|
||||
.flatMap(filter -> filter.getFilter().stream().map(ext -> Pair.of(ext, filter)))
|
||||
.collect(Collectors.groupingBy(
|
||||
pair -> pair.getKey(),
|
||||
Collectors.mapping(pair -> pair.getValue(),
|
||||
Collectors.toSet())));
|
||||
|
||||
private static final String FILE_VIEW_EXT_TYPE_ID = "FILE_VIEW_BY_EXT";
|
||||
|
||||
@@ -88,6 +98,9 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final Cache<SearchParams<Object>, SearchResultsDTO> searchParamsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).expireAfterAccess(CACHE_DURATION, CACHE_DURATION_UNITS).build();
|
||||
private final TreeCounts<DAOEvent> treeCounts = new TreeCounts<>();
|
||||
|
||||
private SleuthkitCase getCase() throws NoCurrentCaseException {
|
||||
return Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
}
|
||||
@@ -99,7 +112,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<FileTypeExtensionsSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchExtensionSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
@@ -110,7 +123,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<FileTypeMimeSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchMimeSearchResultsDTOs(key.getMimeType(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
@@ -121,7 +134,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<FileTypeSizeSearchParams> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchSizeSearchResultsDTOs(key.getSizeFilter(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
@@ -154,8 +167,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
}
|
||||
|
||||
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) eventData;
|
||||
String extension = extEvt.getExtension().toLowerCase();
|
||||
return key.getFilter().getFilter().contains(extension)
|
||||
return key.getFilter().equals(extEvt.getExtensionFilter())
|
||||
&& (key.getDataSourceId() == null || key.getDataSourceId().equals(extEvt.getDataSourceId()));
|
||||
}
|
||||
|
||||
@@ -219,6 +231,22 @@ public class ViewsDAO extends AbstractDAO {
|
||||
.collect(Collectors.joining(", ")) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If user preference of hide known files, returns sql and clause to
|
||||
* hide known files or returns empty string otherwise.
|
||||
*/
|
||||
private String getHideKnownAndClause() {
|
||||
return (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known <> " + TskData.FileKnown.KNOWN.getFileKnownValue() + ") ") : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A clause (no 'and' or 'where' prefixed) indicating the dir_type
|
||||
* is regular.
|
||||
*/
|
||||
private String getRegDirTypeClause() {
|
||||
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a clause that will filter out files that aren't to be counted in
|
||||
* the file extensions view.
|
||||
@@ -226,8 +254,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @return The filter that will need to be proceeded with 'where' or 'and'.
|
||||
*/
|
||||
private String getBaseFileExtensionFilter() {
|
||||
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
|
||||
+ (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known <> " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "");
|
||||
return getRegDirTypeClause() + getHideKnownAndClause();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,6 +274,22 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return whereClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The TSK_DB_FILES_TYPE_ENUm values allowed for mime type view
|
||||
* items.
|
||||
*/
|
||||
private Set<TskData.TSK_DB_FILES_TYPE_ENUM> getMimeDbFilesTypes() {
|
||||
return Stream.of(
|
||||
TskData.TSK_DB_FILES_TYPE_ENUM.FS,
|
||||
TskData.TSK_DB_FILES_TYPE_ENUM.CARVED,
|
||||
TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED,
|
||||
TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE,
|
||||
TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL,
|
||||
(hideSlackFilesInViewsTree() ? null : (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)))
|
||||
.filter(ordinal -> ordinal != null)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a statement to be proceeded with 'where' or 'and' that will
|
||||
* filter results to the provided filter and data source id (if non null).
|
||||
@@ -269,15 +312,10 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @return A statement to be proceeded with 'and' or 'where'.
|
||||
*/
|
||||
private String getBaseFileMimeFilter() {
|
||||
return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")"
|
||||
+ (hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "")
|
||||
return getRegDirTypeClause()
|
||||
+ getHideKnownAndClause()
|
||||
+ " AND (type IN ("
|
||||
+ TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + ","
|
||||
+ TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + ","
|
||||
+ TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + ","
|
||||
+ TskData.TSK_DB_FILES_TYPE_ENUM.LAYOUT_FILE.ordinal() + ","
|
||||
+ TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()
|
||||
+ (hideSlackFilesInViewsTree() ? "" : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()))
|
||||
+ getMimeDbFilesTypes().stream().map(v -> Integer.toString(v.ordinal())).collect(Collectors.joining(", "))
|
||||
+ "))";
|
||||
}
|
||||
|
||||
@@ -354,8 +392,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
*/
|
||||
private String getBaseFileSizeFilter() {
|
||||
// Ignore unallocated block files.
|
||||
return "(type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"
|
||||
+ ((hideKnownFilesInViewsTree() ? (" AND (known IS NULL OR known != " + TskData.FileKnown.KNOWN.getFileKnownValue() + ")") : "")); //NON-NLS
|
||||
return "(type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")" + getHideKnownAndClause();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,6 +427,20 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<FileTypeExtensionsSearchParams> getFileExtCounts(Collection<FileExtSearchFilter> filters, Long dataSourceId) throws IllegalArgumentException, ExecutionException {
|
||||
Set<FileExtSearchFilter> indeterminateFilters = new HashSet<>();
|
||||
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof FileTypeExtensionsEvent) {
|
||||
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) evt;
|
||||
if (dataSourceId == null || Objects.equals(extEvt.getDataSourceId(), dataSourceId)) {
|
||||
for (FileExtSearchFilter filter : filters) {
|
||||
if (filter.getFilter().contains(evt)) {
|
||||
indeterminateFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<FileExtSearchFilter, String> whereClauses = filters.stream()
|
||||
.collect(Collectors.toMap(
|
||||
filter -> filter,
|
||||
@@ -399,12 +450,11 @@ public class ViewsDAO extends AbstractDAO {
|
||||
|
||||
List<TreeItemDTO<FileTypeExtensionsSearchParams>> treeList = countsByFilter.entrySet().stream()
|
||||
.map(entry -> {
|
||||
return new TreeItemDTO<>(
|
||||
"FILE_EXT",
|
||||
new FileTypeExtensionsSearchParams(entry.getKey(), dataSourceId),
|
||||
entry.getKey(),
|
||||
entry.getKey().getDisplayName(),
|
||||
TreeDisplayCount.getDeterminate(entry.getValue()));
|
||||
TreeDisplayCount displayCount = indeterminateFilters.contains(entry.getKey())
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(entry.getValue());
|
||||
|
||||
return createExtensionTreeItem(entry.getKey(), dataSourceId, displayCount);
|
||||
})
|
||||
.sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName()))
|
||||
.collect(Collectors.toList());
|
||||
@@ -412,6 +462,24 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return new TreeResultsDTO<>(treeList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an extension tree item.
|
||||
*
|
||||
* @param filter The extension filter.
|
||||
* @param dataSourceId The data source id or null.
|
||||
* @param displayCount The count to display.
|
||||
*
|
||||
* @return The extension tree item.
|
||||
*/
|
||||
private TreeItemDTO<FileTypeExtensionsSearchParams> createExtensionTreeItem(FileExtSearchFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
FileTypeExtensionsSearchParams.getTypeId(),
|
||||
new FileTypeExtensionsSearchParams(filter, dataSourceId),
|
||||
filter,
|
||||
filter.getDisplayName(),
|
||||
displayCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns counts for file size categories.
|
||||
*
|
||||
@@ -424,6 +492,16 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<FileTypeSizeSearchParams> getFileSizeCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
|
||||
Set<FileSizeFilter> indeterminateFilters = new HashSet<>();
|
||||
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof FileTypeSizeEvent) {
|
||||
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) evt;
|
||||
if (dataSourceId == null || Objects.equals(sizeEvt.getDataSourceId(), dataSourceId)) {
|
||||
indeterminateFilters.add(sizeEvt.getSizeFilter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<FileSizeFilter, String> whereClauses = Stream.of(FileSizeFilter.values())
|
||||
.collect(Collectors.toMap(
|
||||
filter -> filter,
|
||||
@@ -433,12 +511,11 @@ public class ViewsDAO extends AbstractDAO {
|
||||
|
||||
List<TreeItemDTO<FileTypeSizeSearchParams>> treeList = countsByFilter.entrySet().stream()
|
||||
.map(entry -> {
|
||||
return new TreeItemDTO<>(
|
||||
"FILE_SIZE",
|
||||
new FileTypeSizeSearchParams(entry.getKey(), dataSourceId),
|
||||
entry.getKey(),
|
||||
entry.getKey().getDisplayName(),
|
||||
TreeDisplayCount.getDeterminate(entry.getValue()));
|
||||
TreeDisplayCount displayCount = indeterminateFilters.contains(entry.getKey())
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(entry.getValue());
|
||||
|
||||
return createSizeTreeItem(entry.getKey(), dataSourceId, displayCount);
|
||||
})
|
||||
.sorted((a, b) -> a.getDisplayName().compareToIgnoreCase(b.getDisplayName()))
|
||||
.collect(Collectors.toList());
|
||||
@@ -479,6 +556,23 @@ public class ViewsDAO extends AbstractDAO {
|
||||
|
||||
return new TreeResultsDTO<>(treeList);
|
||||
}
|
||||
/**
|
||||
* Creates a size tree item.
|
||||
*
|
||||
* @param filter The file size filter.
|
||||
* @param dataSourceId The data source id.
|
||||
* @param displayCount The display count.
|
||||
*
|
||||
* @return The tree item.
|
||||
*/
|
||||
private TreeItemDTO<FileTypeSizeSearchParams> createSizeTreeItem(FileSizeFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
FileTypeSizeSearchParams.getTypeId(),
|
||||
new FileTypeSizeSearchParams(filter, dataSourceId),
|
||||
filter,
|
||||
filter.getDisplayName(),
|
||||
displayCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns counts for file mime type categories.
|
||||
@@ -497,6 +591,22 @@ public class ViewsDAO extends AbstractDAO {
|
||||
String prefixWithSlash = StringUtils.isNotBlank(prefix) ? prefix.replaceAll("/", "") + "/" : null;
|
||||
String likeItem = StringUtils.isNotBlank(prefixWithSlash) ? prefixWithSlash.replaceAll("%", "") + "%" : null;
|
||||
|
||||
Set<String> indeterminateMimeTypes = new HashSet<>();
|
||||
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof FileTypeMimeEvent) {
|
||||
FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) evt;
|
||||
if ((dataSourceId == null || Objects.equals(mimeEvt.getDataSourceId(), dataSourceId))
|
||||
&& (prefixWithSlash == null || mimeEvt.getMimeType().startsWith(prefixWithSlash))) {
|
||||
|
||||
String mimePortion = prefixWithSlash != null
|
||||
? mimeEvt.getMimeType().substring(prefixWithSlash.length())
|
||||
: mimeEvt.getMimeType().substring(0, mimeEvt.getMimeType().indexOf("/"));
|
||||
|
||||
indeterminateMimeTypes.add(mimePortion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String baseFilter = "WHERE " + getBaseFileMimeFilter()
|
||||
+ getDataSourceAndClause(dataSourceId)
|
||||
+ (StringUtils.isNotBlank(prefix) ? " AND mime_type LIKE ? " : " AND mime_type IS NOT NULL ");
|
||||
@@ -552,12 +662,11 @@ public class ViewsDAO extends AbstractDAO {
|
||||
? entry.getKey().substring(prefixWithSlash.length())
|
||||
: entry.getKey();
|
||||
|
||||
return new TreeItemDTO<>(
|
||||
"FILE_MIME_TYPE",
|
||||
new FileTypeMimeSearchParams(entry.getKey(), dataSourceId),
|
||||
name,
|
||||
name,
|
||||
TreeDisplayCount.getDeterminate(entry.getValue()));
|
||||
TreeDisplayCount displayCount = indeterminateMimeTypes.contains(name)
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(entry.getValue());
|
||||
|
||||
return createMimeTreeItem(entry.getKey(), name, dataSourceId, displayCount);
|
||||
})
|
||||
.sorted((a, b) -> stringCompare(a.getSearchParams().getMimeType(), b.getSearchParams().getMimeType()))
|
||||
.collect(Collectors.toList());
|
||||
@@ -571,6 +680,26 @@ public class ViewsDAO extends AbstractDAO {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mime type tree item.
|
||||
*
|
||||
* @param fullMime The full mime type.
|
||||
* @param mimeName The mime type segment that will be displayed (suffix
|
||||
* or prefix).
|
||||
* @param dataSourceId The data source id.
|
||||
* @param displayCount The count to display.
|
||||
*
|
||||
* @return The created tree item.
|
||||
*/
|
||||
private TreeItemDTO<FileTypeMimeSearchParams> createMimeTreeItem(String fullMime, String mimeName, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
FileTypeMimeSearchParams.getTypeId(),
|
||||
new FileTypeMimeSearchParams(fullMime, dataSourceId),
|
||||
mimeName,
|
||||
mimeName,
|
||||
displayCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides case insensitive comparator integer for strings that may be
|
||||
* null.
|
||||
@@ -735,11 +864,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return new BaseSearchResultsDTO(FILE_VIEW_EXT_TYPE_ID, displayName, FileSystemColumnUtils.getColumnKeysForAbstractfile(), fileRows, AbstractFile.class.getName(), startItem, totalResultsCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
}
|
||||
|
||||
private Pair<String, String> getMimePieces(String mimeType) {
|
||||
int idx = mimeType.indexOf("/");
|
||||
String mimePrefix = idx > 0 ? mimeType.substring(0, idx) : mimeType;
|
||||
@@ -747,169 +871,142 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return Pair.of(mimePrefix, mimeSuffix);
|
||||
}
|
||||
|
||||
private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) {
|
||||
if (daoEvent instanceof FileTypeExtensionsEvent) {
|
||||
FileTypeExtensionsEvent extEvt = (FileTypeExtensionsEvent) daoEvent;
|
||||
return createExtensionTreeItem(extEvt.getExtensionFilter(), extEvt.getDataSourceId(), count);
|
||||
} else if (daoEvent instanceof FileTypeMimeEvent) {
|
||||
FileTypeMimeEvent mimeEvt = (FileTypeMimeEvent) daoEvent;
|
||||
Pair<String, String> mimePieces = getMimePieces(mimeEvt.getMimeType());
|
||||
String mimeName = mimePieces.getRight() == null ? mimePieces.getLeft() : mimePieces.getRight();
|
||||
return createMimeTreeItem(mimeEvt.getMimeType(), mimeName, mimeEvt.getDataSourceId(), count);
|
||||
} else if (daoEvent instanceof FileTypeSizeEvent) {
|
||||
FileTypeSizeEvent sizeEvt = (FileTypeSizeEvent) daoEvent;
|
||||
return createSizeTreeItem(sizeEvt.getSizeFilter(), sizeEvt.getDataSourceId(), count);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> handleIngestComplete() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
handleIngestComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
return SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
|
||||
(daoEvt, count) -> createTreeItem(daoEvt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
// GVDTODO
|
||||
return Collections.emptySet();
|
||||
return SubDAOUtils.getRefreshEvents(this.treeCounts,
|
||||
(daoEvt, count) -> createTreeItem(daoEvt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
// GVDTODO maps may not be necessary now that this isn't processing a list of events.
|
||||
Map<String, Set<Long>> fileExtensionDsMap = new HashMap<>();
|
||||
Map<String, Map<String, Set<Long>>> mimeTypeDsMap = new HashMap<>();
|
||||
Map<FileSizeFilter, Set<Long>> fileSizeDsMap = new HashMap<>();
|
||||
|
||||
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
|
||||
if (af == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// create an extension mapping if extension present
|
||||
if (!StringUtils.isBlank(af.getNameExtension())) {
|
||||
fileExtensionDsMap
|
||||
.computeIfAbsent("." + af.getNameExtension(), (k) -> new HashSet<>())
|
||||
.add(af.getDataSourceObjectId());
|
||||
}
|
||||
|
||||
// create a mime type mapping if mime type present
|
||||
if (!StringUtils.isBlank(af.getMIMEType())) {
|
||||
Pair<String, String> mimePieces = getMimePieces(af.getMIMEType());
|
||||
mimeTypeDsMap
|
||||
.computeIfAbsent(mimePieces.getKey(), (k) -> new HashMap<>())
|
||||
.computeIfAbsent(mimePieces.getValue(), (k) -> new HashSet<>())
|
||||
.add(af.getDataSourceObjectId());
|
||||
}
|
||||
|
||||
// create a size mapping if size present
|
||||
FileSizeFilter sizeFilter = Stream.of(FileSizeFilter.values())
|
||||
.filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (sizeFilter != null) {
|
||||
fileSizeDsMap
|
||||
.computeIfAbsent(sizeFilter, (k) -> new HashSet<>())
|
||||
.add(af.getDataSourceObjectId());
|
||||
}
|
||||
|
||||
if (fileExtensionDsMap.isEmpty() && mimeTypeDsMap.isEmpty() && fileSizeDsMap.isEmpty()) {
|
||||
} else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
clearRelevantCacheEntries(fileExtensionDsMap, mimeTypeDsMap, fileSizeDsMap);
|
||||
long dsId = af.getDataSourceObjectId();
|
||||
|
||||
return getDAOEvents(fileExtensionDsMap, mimeTypeDsMap, fileSizeDsMap);
|
||||
// create an extension mapping if extension present
|
||||
Set<FileExtSearchFilter> evtExtFilters = (StringUtils.isBlank(af.getNameExtension()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType()))
|
||||
? Collections.emptySet()
|
||||
: EXTENSION_FILTER_MAP.getOrDefault("." + af.getNameExtension(), Collections.emptySet());
|
||||
|
||||
// create a mime type mapping if mime type present
|
||||
String evtMimeType = (StringUtils.isBlank(af.getMIMEType()) || !TSK_FS_NAME_TYPE_ENUM.REG.equals(af.getDirType())) || !getMimeDbFilesTypes().contains(af.getType())
|
||||
? null
|
||||
: af.getMIMEType();
|
||||
|
||||
// create a size mapping if size present in filters
|
||||
FileSizeFilter evtFileSize = TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.equals(af.getType())
|
||||
? null
|
||||
: Stream.of(FileSizeFilter.values())
|
||||
.filter(filter -> af.getSize() >= filter.getMinBound() && (filter.getMaxBound() == null || af.getSize() < filter.getMaxBound()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (evtExtFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(Predicate<Object>) (searchParams) -> searchParamsMatchEvent(evtExtFilters, evtMimeType, evtFileSize, dsId, searchParams));
|
||||
|
||||
return getDAOEvents(evtExtFilters, evtMimeType, evtFileSize, dsId);
|
||||
}
|
||||
|
||||
private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters,
|
||||
String evtMimeType,
|
||||
FileSizeFilter evtFileSize,
|
||||
long dsId,
|
||||
Object searchParams) {
|
||||
|
||||
if (searchParams instanceof FileTypeExtensionsSearchParams) {
|
||||
FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) searchParams;
|
||||
return evtExtFilters.contains(extParams.getFilter())
|
||||
&& (extParams.getDataSourceId() == null || Objects.equals(extParams.getDataSourceId(), dsId));
|
||||
|
||||
} else if (searchParams instanceof FileTypeMimeSearchParams) {
|
||||
FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) searchParams;
|
||||
return evtMimeType != null && evtMimeType.startsWith(mimeParams.getMimeType())
|
||||
&& (mimeParams.getDataSourceId() == null || Objects.equals(mimeParams.getDataSourceId(), dsId));
|
||||
|
||||
} else if (searchParams instanceof FileTypeSizeSearchParams) {
|
||||
FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) searchParams;
|
||||
return Objects.equals(sizeParams.getSizeFilter(), evtFileSize)
|
||||
&& (sizeParams.getDataSourceId() == null || Objects.equals(sizeParams.getDataSourceId(), dsId));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Clears relevant cache entries from cache based on digest of autopsy
|
||||
* events.
|
||||
*
|
||||
* @param fileExtensionDsMap Maps the file extension to the data sources
|
||||
* where files were found with that extension.
|
||||
* @param mimeTypeDsMap Maps the mime type to the data sources where
|
||||
* files were found with that mime type.
|
||||
* @param fileSizeDsMap Maps the size to the data sources where files
|
||||
* @param extFilters The set of affected extension filters.
|
||||
* @param mimeType The affected mime type or null.
|
||||
* @param sizeFilter The affected size filter or null.
|
||||
* @param dsId The file object id.
|
||||
*
|
||||
* @return The list of affected dao events.
|
||||
*/
|
||||
private Set<DAOEvent> getDAOEvents(Map<String, Set<Long>> fileExtensionDsMap,
|
||||
Map<String, Map<String, Set<Long>>> mimeTypeDsMap,
|
||||
Map<FileSizeFilter, Set<Long>> fileSizeDsMap) {
|
||||
private Set<DAOEvent> getDAOEvents(Set<FileExtSearchFilter> extFilters,
|
||||
String mimeType,
|
||||
FileSizeFilter sizeFilter,
|
||||
long dsId) {
|
||||
|
||||
Stream<DAOEvent> fileExtStream = fileExtensionDsMap.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(dsId -> new FileTypeExtensionsEvent(entry.getKey(), dsId)));
|
||||
List<DAOEvent> daoEvents = extFilters.stream()
|
||||
.map(extFilter -> new FileTypeExtensionsEvent(extFilter, dsId))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<DAOEvent> fileMimeList = new HashSet<>();
|
||||
for (Entry<String, Map<String, Set<Long>>> prefixEntry : mimeTypeDsMap.entrySet()) {
|
||||
String mimePrefix = prefixEntry.getKey();
|
||||
for (Entry<String, Set<Long>> suffixEntry : prefixEntry.getValue().entrySet()) {
|
||||
String mimeSuffix = suffixEntry.getKey();
|
||||
for (long dsId : suffixEntry.getValue()) {
|
||||
String mimeType = mimePrefix + (mimeSuffix == null ? "" : ("/" + mimeSuffix));
|
||||
fileMimeList.add(new FileTypeMimeEvent(mimeType, dsId));
|
||||
}
|
||||
}
|
||||
if (mimeType != null) {
|
||||
daoEvents.add(new FileTypeMimeEvent(mimeType, dsId));
|
||||
}
|
||||
|
||||
Stream<DAOEvent> fileSizeStream = fileSizeDsMap.entrySet().stream()
|
||||
.flatMap(entry -> entry.getValue().stream().map(dsId -> new FileTypeSizeEvent(entry.getKey(), dsId)));
|
||||
if (sizeFilter != null) {
|
||||
daoEvents.add(new FileTypeSizeEvent(sizeFilter, dsId));
|
||||
}
|
||||
|
||||
return Stream.of(fileExtStream, fileMimeList.stream(), fileSizeStream)
|
||||
.flatMap(stream -> stream)
|
||||
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(daoEvents, treeEvents)
|
||||
.flatMap(lst -> lst.stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears relevant cache entries from cache based on digest of autopsy
|
||||
* events.
|
||||
*
|
||||
* @param fileExtensionDsMap Maps the file extension to the data sources
|
||||
* where files were found with that extension.
|
||||
* @param mimeTypeDsMap Maps the mime type to the data sources where
|
||||
* files were found with that mime type.
|
||||
* @param fileSizeDsMap Maps the size to the data sources where files
|
||||
* were found within that size filter.
|
||||
*/
|
||||
private void clearRelevantCacheEntries(Map<String, Set<Long>> fileExtensionDsMap,
|
||||
Map<String, Map<String, Set<Long>>> mimeTypeDsMap,
|
||||
Map<FileSizeFilter, Set<Long>> fileSizeDsMap) {
|
||||
|
||||
// invalidate cache entries that are affected by events
|
||||
ConcurrentMap<SearchParams<?>, SearchResultsDTO> concurrentMap = this.searchParamsCache.asMap();
|
||||
concurrentMap.forEach((k, v) -> {
|
||||
Object baseParams = k.getParamData();
|
||||
if (baseParams instanceof FileTypeExtensionsSearchParams) {
|
||||
FileTypeExtensionsSearchParams extParams = (FileTypeExtensionsSearchParams) baseParams;
|
||||
// if search params have a filter where extension is present and the data source id is null or ==
|
||||
boolean isMatch = extParams.getFilter().getFilter().stream().anyMatch((ext) -> {
|
||||
Set<Long> dsIds = fileExtensionDsMap.get(ext);
|
||||
return (dsIds != null && (extParams.getDataSourceId() == null || dsIds.contains(extParams.getDataSourceId())));
|
||||
});
|
||||
|
||||
if (isMatch) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
} else if (baseParams instanceof FileTypeMimeSearchParams) {
|
||||
FileTypeMimeSearchParams mimeParams = (FileTypeMimeSearchParams) baseParams;
|
||||
Pair<String, String> mimePieces = getMimePieces(mimeParams.getMimeType());
|
||||
Map<String, Set<Long>> suffixes = mimeTypeDsMap.get(mimePieces.getKey());
|
||||
if (suffixes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if search params is top level mime prefix (without suffix) and data source is null or ==.
|
||||
if (mimePieces.getValue() == null
|
||||
&& (mimeParams.getDataSourceId() == null
|
||||
|| suffixes.values().stream().flatMap(set -> set.stream()).anyMatch(ds -> Objects.equals(mimeParams.getDataSourceId(), ds)))) {
|
||||
|
||||
concurrentMap.remove(k);
|
||||
// otherwise, see if suffix is present
|
||||
} else {
|
||||
Set<Long> dataSources = suffixes.get(mimePieces.getValue());
|
||||
if (dataSources != null && (mimeParams.getDataSourceId() == null || dataSources.contains(mimeParams.getDataSourceId()))) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (baseParams instanceof FileTypeSizeSearchParams) {
|
||||
FileTypeSizeSearchParams sizeParams = (FileTypeSizeSearchParams) baseParams;
|
||||
Set<Long> dataSources = fileSizeDsMap.get(sizeParams.getSizeFilter());
|
||||
if (dataSources != null && (sizeParams.getDataSourceId() == null || dataSources.contains(sizeParams.getDataSourceId()))) {
|
||||
concurrentMap.remove(k);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for file types by extension.
|
||||
*/
|
||||
|
||||
+26
-10
@@ -19,6 +19,8 @@
|
||||
package org.sleuthkit.autopsy.mainui.datamodel.events;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Host;
|
||||
|
||||
/**
|
||||
* An event signaling that children files were added or removed from the given
|
||||
@@ -26,26 +28,40 @@ import java.util.Objects;
|
||||
*/
|
||||
public class FileSystemContentEvent implements DAOEvent {
|
||||
|
||||
private final Long contentObjectId;
|
||||
private final Content content;
|
||||
private final Long parentObjId;
|
||||
private final Host parentHost;
|
||||
|
||||
public FileSystemContentEvent(Content content, Long parentObjId, Host parentHost) {
|
||||
this.content = content;
|
||||
this.parentObjId = parentObjId;
|
||||
this.parentHost = parentHost;
|
||||
}
|
||||
|
||||
public Long getParentObjId() {
|
||||
return parentObjId;
|
||||
}
|
||||
|
||||
public Host getParentHost() {
|
||||
return parentHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param contentObjectId The parent content object id. If null, performs
|
||||
* full refresh of file tree.
|
||||
* @return The content associated with the event, if null, triggers a full
|
||||
* refresh.
|
||||
*/
|
||||
public FileSystemContentEvent(Long contentObjectId) {
|
||||
this.contentObjectId = contentObjectId;
|
||||
public Content getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Long getContentObjectId() {
|
||||
return contentObjectId;
|
||||
return (content == null) ? null : content.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 67 * hash + Objects.hashCode(this.contentObjectId);
|
||||
hash = 71 * hash + Objects.hashCode(this.content);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -61,7 +77,7 @@ public class FileSystemContentEvent implements DAOEvent {
|
||||
return false;
|
||||
}
|
||||
final FileSystemContentEvent other = (FileSystemContentEvent) obj;
|
||||
if (!Objects.equals(this.contentObjectId, other.contentObjectId)) {
|
||||
if (!Objects.equals(this.content, other.content)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
+11
-8
@@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.mainui.datamodel.events;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileExtSearchFilter;
|
||||
|
||||
/**
|
||||
* An event to signal that files have been added or removed
|
||||
@@ -26,16 +27,16 @@ import java.util.Objects;
|
||||
*/
|
||||
public class FileTypeExtensionsEvent implements DAOEvent {
|
||||
|
||||
private final String extension;
|
||||
private final FileExtSearchFilter extensionFilter;
|
||||
private final long dataSourceId;
|
||||
|
||||
public FileTypeExtensionsEvent(String extension, long dataSourceId) {
|
||||
this.extension = extension;
|
||||
public FileTypeExtensionsEvent(FileExtSearchFilter extensionFilter, long dataSourceId) {
|
||||
this.extensionFilter = extensionFilter;
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
public String getExtension() {
|
||||
return extension;
|
||||
public FileExtSearchFilter getExtensionFilter() {
|
||||
return extensionFilter;
|
||||
}
|
||||
|
||||
public long getDataSourceId() {
|
||||
@@ -45,8 +46,8 @@ public class FileTypeExtensionsEvent implements DAOEvent {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 59 * hash + Objects.hashCode(this.extension);
|
||||
hash = 59 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
|
||||
hash = 83 * hash + Objects.hashCode(this.extensionFilter);
|
||||
hash = 83 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -65,12 +66,14 @@ public class FileTypeExtensionsEvent implements DAOEvent {
|
||||
if (this.dataSourceId != other.dataSourceId) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.extension, other.extension)) {
|
||||
if (!Objects.equals(this.extensionFilter, other.extensionFilter)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.RESULT;
|
||||
|
||||
@@ -26,20 +26,36 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
*/
|
||||
public class KeywordHitEvent extends AnalysisResultSetEvent {
|
||||
|
||||
private final String regex;
|
||||
private final String searchString;
|
||||
private final String match;
|
||||
private final int searchType;
|
||||
|
||||
public KeywordHitEvent(String regex, String match, String setName, BlackboardArtifact.Type artifactType, long dataSourceId) {
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param searchString The search string or regex.
|
||||
* @param match The match string.
|
||||
* @param searchType THe search type.
|
||||
* @param setName The set name.
|
||||
* @param artifactType The artifact type.
|
||||
* @param dataSourceId The data source id.
|
||||
*/
|
||||
public KeywordHitEvent(String searchString, String match, int searchType, String setName, BlackboardArtifact.Type artifactType, long dataSourceId) {
|
||||
super(setName, artifactType, dataSourceId);
|
||||
this.regex = regex;
|
||||
this.searchString = searchString;
|
||||
this.match = match;
|
||||
this.searchType = searchType;
|
||||
}
|
||||
|
||||
public String getRegex() {
|
||||
return regex;
|
||||
public String getSearchString() {
|
||||
return searchString;
|
||||
}
|
||||
|
||||
public String getMatch() {
|
||||
return match;
|
||||
}
|
||||
|
||||
public int getSearchType() {
|
||||
return searchType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.mainui.datamodel.events;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* An event to signal that tags have been added or removed on the
|
||||
@@ -28,21 +29,22 @@ import org.sleuthkit.autopsy.mainui.datamodel.TagsSearchParams.TagType;
|
||||
public class TagsEvent implements DAOEvent {
|
||||
|
||||
private final TagType type;
|
||||
private final Long tagNameId;
|
||||
private final TagName tagName;
|
||||
private final Long dataSourceId;
|
||||
|
||||
public TagsEvent(TagType type, Long tagNameId, Long dataSourceId) {
|
||||
public TagsEvent(TagType type, TagName tagName, Long dataSourceId) {
|
||||
this.type = type;
|
||||
this.tagNameId = tagNameId;
|
||||
this.tagName = tagName;
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
|
||||
public TagType getTagType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Long getTagNameId() {
|
||||
return tagNameId;
|
||||
public TagName getTagName() {
|
||||
return tagName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +59,7 @@ public class TagsEvent implements DAOEvent {
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 97 * hash + Objects.hashCode(this.type);
|
||||
hash = 97 * hash + Objects.hashCode(this.tagNameId);
|
||||
hash = 97 * hash + Objects.hashCode(this.tagName);
|
||||
hash = 97 * hash + Objects.hashCode(this.dataSourceId);
|
||||
return hash;
|
||||
}
|
||||
@@ -77,7 +79,7 @@ public class TagsEvent implements DAOEvent {
|
||||
if (this.type != other.type) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.tagNameId, other.tagNameId)) {
|
||||
if (!Objects.equals(this.tagName, other.tagName)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.dataSourceId, other.dataSourceId)) {
|
||||
@@ -86,6 +88,8 @@ public class TagsEvent implements DAOEvent {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.RESULT;
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.KeywordSearchTermParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.KeywordMatchParams;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
@@ -29,11 +30,13 @@ import org.openide.nodes.Children;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
||||
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSetSearchParam;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.KeywordHitSearchParam;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@@ -49,7 +52,8 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
|
||||
private static Set<Integer> SET_TREE_ARTIFACTS = ImmutableSet.of(
|
||||
BlackboardArtifact.Type.TSK_HASHSET_HIT.getTypeID(),
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID(),
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID()
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID(),
|
||||
BlackboardArtifact.Type.TSK_INTERESTING_ITEM.getTypeID()
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -92,14 +96,29 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
|
||||
TreeResultsDTO.TreeItemDTO<AnalysisResultSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSearchParam.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& !AnalysisResultDAO.getIgnoredTreeTypes().contains(originalTreeItem.getSearchParams().getArtifactType())
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
AnalysisResultSearchParam searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new AnalysisResultSearchParam(searchParam.getArtifactType(), this.dataSourceId),
|
||||
searchParam.getArtifactType().getTypeID(),
|
||||
searchParam.getArtifactType().getDisplayName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(AnalysisResultSearchParam o1, AnalysisResultSearchParam o2) {
|
||||
return o1.getArtifactType().getDisplayName().compareTo(o2.getArtifactType().getDisplayName());
|
||||
public int compare(TreeItemDTO<? extends AnalysisResultSearchParam> o1, TreeItemDTO<? extends AnalysisResultSearchParam> o2) {
|
||||
return o1.getSearchParams().getArtifactType().getDisplayName().compareTo(o2.getSearchParams().getArtifactType().getDisplayName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,14 +198,28 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends AnalysisResultSetSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<AnalysisResultSetSearchParam> originalTreeItem = super.getTypedTreeItem(treeEvt, AnalysisResultSetSearchParam.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& originalTreeItem.getSearchParams().getArtifactType().equals(this.artifactType)
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
AnalysisResultSetSearchParam searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSetSearchParam.getTypeId(),
|
||||
new AnalysisResultSetSearchParam(this.artifactType, this.dataSourceId, searchParam.getSetName()),
|
||||
searchParam.getSetName(),
|
||||
searchParam.getSetName() == null ? nullSetName : searchParam.getSetName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(AnalysisResultSetSearchParam o1, AnalysisResultSetSearchParam o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSetName(), o2.getSetName());
|
||||
public int compare(TreeItemDTO<? extends AnalysisResultSetSearchParam> o1, TreeItemDTO<? extends AnalysisResultSetSearchParam> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getSetName(), o2.getSearchParams().getSetName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,14 +309,38 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends KeywordSearchTermParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<KeywordSearchTermParams> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordSearchTermParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& Objects.equals(originalTreeItem.getSearchParams().getSetName(), this.setParams.getSetName())
|
||||
&& (this.setParams.getDataSourceId() == null
|
||||
|| Objects.equals(this.setParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
KeywordSearchTermParams searchParam = originalTreeItem.getSearchParams();
|
||||
String searchTermDisplayName = MainDAO.getInstance().getAnalysisResultDAO()
|
||||
.getSearchTermDisplayName(searchParam.getSearchTerm(), searchParam.getSearchType());
|
||||
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
KeywordSearchTermParams.getTypeId(),
|
||||
new KeywordSearchTermParams(
|
||||
this.setParams.getSetName(),
|
||||
searchParam.getSearchTerm(),
|
||||
searchParam.getSearchType(),
|
||||
searchParam.hasChildren(),
|
||||
this.setParams.getDataSourceId()
|
||||
),
|
||||
searchTermDisplayName,
|
||||
searchTermDisplayName,
|
||||
originalTreeItem.getDisplayCount()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(KeywordSearchTermParams o1, KeywordSearchTermParams o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchTerm(), o2.getSearchTerm());
|
||||
public int compare(TreeItemDTO<? extends KeywordSearchTermParams> o1, TreeItemDTO<? extends KeywordSearchTermParams> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getSearchTerm(), o2.getSearchParams().getSearchTerm());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -356,14 +413,36 @@ public class AnalysisResultTypeFactory extends TreeChildFactory<AnalysisResultSe
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends KeywordMatchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends KeywordMatchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<KeywordMatchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, KeywordMatchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& Objects.equals(originalTreeItem.getSearchParams().getSetName(), this.setParams.getSetName())
|
||||
&& (this.setParams.getDataSourceId() == null
|
||||
|| Objects.equals(this.setParams.getDataSourceId(), originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
KeywordMatchParams searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
KeywordMatchParams.getTypeId(),
|
||||
new KeywordMatchParams(
|
||||
this.setParams.getSetName(),
|
||||
this.setParams.getSearchTerm(),
|
||||
searchParam.getKeywordMatch(),
|
||||
this.setParams.getSearchType(),
|
||||
this.setParams.getDataSourceId()
|
||||
),
|
||||
searchParam.getKeywordMatch(),
|
||||
searchParam.getKeywordMatch() == null ? "" : searchParam.getKeywordMatch(),
|
||||
originalTreeItem.getDisplayCount()
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(KeywordMatchParams o1, KeywordMatchParams o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getKeywordMatch(), o2.getKeywordMatch());
|
||||
public int compare(TreeItemDTO<? extends KeywordMatchParams> o1, TreeItemDTO<? extends KeywordMatchParams> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getKeywordMatch(), o2.getSearchParams().getKeywordMatch());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
|
||||
|
||||
DataArtifactSearchParam searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeItemDTO<>(
|
||||
BlackboardArtifact.Category.DATA_ARTIFACT.name(),
|
||||
DataArtifactSearchParam.getTypeId(),
|
||||
new DataArtifactSearchParam(searchParam.getArtifactType(), this.dataSourceId),
|
||||
searchParam.getArtifactType().getTypeID(),
|
||||
MainDAO.getInstance().getDataArtifactsDAO().getDisplayName(searchParam.getArtifactType()),
|
||||
@@ -87,9 +87,9 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(DataArtifactSearchParam o1, DataArtifactSearchParam o2) {
|
||||
public int compare(TreeItemDTO<? extends DataArtifactSearchParam> o1, TreeItemDTO<? extends DataArtifactSearchParam> o2) {
|
||||
DataArtifactDAO dao = MainDAO.getInstance().getDataArtifactsDAO();
|
||||
return dao.getDisplayName(o1.getArtifactType()).compareToIgnoreCase(dao.getDisplayName(o2.getArtifactType()));
|
||||
return dao.getDisplayName(o1.getSearchParams().getArtifactType()).compareToIgnoreCase(dao.getDisplayName(o2.getSearchParams().getArtifactType()));
|
||||
}
|
||||
|
||||
private static String getIconPath(BlackboardArtifact.Type artType) {
|
||||
@@ -206,8 +206,8 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(CommAccountsSearchParams o1, CommAccountsSearchParams o2) {
|
||||
return o1.getType().getDisplayName().compareToIgnoreCase(o2.getType().getDisplayName());
|
||||
public int compare(TreeItemDTO<? extends CommAccountsSearchParams> o1, TreeItemDTO<? extends CommAccountsSearchParams> o2) {
|
||||
return o1.getSearchParams().getType().getDisplayName().compareToIgnoreCase(o2.getSearchParams().getType().getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
@@ -35,9 +36,12 @@ import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction;
|
||||
import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemContentSearchParam;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemColumnUtils;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileSystemDAO.FileSystemTreeItem;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MediaTypeUtils;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.CARVED_FILE;
|
||||
import static org.sleuthkit.autopsy.mainui.nodes.NodeIconUtil.DELETED_FILE;
|
||||
@@ -139,14 +143,31 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
// GVDTODO
|
||||
if (treeEvt instanceof FileSystemTreeEvent) {
|
||||
FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt;
|
||||
// when getContentObjectId == null, trigger refresh, otherwise, see if common parent
|
||||
if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null
|
||||
|| (Objects.equals(this.host, fsTreeEvent.getParentHost())
|
||||
&& Objects.equals(this.contentId, fsTreeEvent.getParentContentId()))) {
|
||||
|
||||
return fsTreeEvent.getItemRecord();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileSystemContentSearchParam o1, FileSystemContentSearchParam o2) {
|
||||
// GVDTODO
|
||||
return 0;
|
||||
public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) {
|
||||
if (o1 instanceof FileSystemTreeItem && o2 instanceof FileSystemTreeItem) {
|
||||
FileSystemTreeItem fs1 = (FileSystemTreeItem) o1;
|
||||
FileSystemTreeItem fs2 = (FileSystemTreeItem) o2;
|
||||
// ordering taken from SELECT_FILES_BY_PARENT in SleuthkitCase
|
||||
if (fs1.getMetaType().getValue() != fs2.getMetaType().getValue()) {
|
||||
return -Short.compare(fs1.getMetaType().getValue(), fs2.getMetaType().getValue());
|
||||
}
|
||||
}
|
||||
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,14 +216,23 @@ public class FileSystemFactory extends TreeChildFactory<FileSystemContentSearchP
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileSystemContentSearchParam> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
// GVDTODO
|
||||
if (treeEvt instanceof FileSystemTreeEvent) {
|
||||
FileSystemTreeEvent fsTreeEvent = (FileSystemTreeEvent) treeEvt;
|
||||
// when getContentObjectId == null, trigger refresh, otherwise, see if common parent
|
||||
if (fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId() == null
|
||||
|| Objects.equals(fsTreeEvent.getItemRecord().getSearchParams().getContentObjectId(), dataSourceId)) {
|
||||
|
||||
return fsTreeEvent.getItemRecord();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileSystemContentSearchParam o1, FileSystemContentSearchParam o2) {
|
||||
// GVDTODO
|
||||
return 0;
|
||||
public int compare(TreeItemDTO<? extends FileSystemContentSearchParam> o1, TreeItemDTO<? extends FileSystemContentSearchParam> o2) {
|
||||
return o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
/**
|
||||
* Factory for populating child nodes in a tree based on TreeResultsDTO
|
||||
*/
|
||||
public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object> implements Comparator<T> {
|
||||
public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object> implements Comparator<TreeItemDTO<? extends T>> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TreeChildFactory.class.getName());
|
||||
|
||||
@@ -140,7 +140,8 @@ public abstract class TreeChildFactory<T> extends ChildFactory.Detachable<Object
|
||||
// insert in sorted position
|
||||
int insertIndex = 0;
|
||||
for (; insertIndex < this.curItemsList.size(); insertIndex++) {
|
||||
if (this.compare(item.getSearchParams(), this.curItemsList.get(insertIndex).getSearchParams()) < 0) {
|
||||
TreeItemDTO<? extends T> curItem = this.curItemsList.get(insertIndex);
|
||||
if (this.compare(item, curItem) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.nodes;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.openide.nodes.Children;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultSearchParam;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileExtDocumentFilter;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileExtExecutableFilter;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileExtRootFilter;
|
||||
@@ -36,8 +36,8 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -74,14 +74,27 @@ public class ViewsTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeSizeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<FileTypeSizeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeSizeSearchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
FileTypeSizeSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new FileTypeSizeSearchParams(searchParam.getSizeFilter(), this.dataSourceId),
|
||||
searchParam.getSizeFilter(),
|
||||
searchParam.getSizeFilter().getDisplayName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileTypeSizeSearchParams o1, FileTypeSizeSearchParams o2) {
|
||||
return Integer.compare(o1.getSizeFilter().getId(), o2.getSizeFilter().getId());
|
||||
public int compare(TreeItemDTO<? extends FileTypeSizeSearchParams> o1, TreeItemDTO<? extends FileTypeSizeSearchParams> o2) {
|
||||
return Integer.compare(o1.getSearchParams().getSizeFilter().getId(), o2.getSearchParams().getSizeFilter().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,14 +146,34 @@ public class ViewsTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
String mimePrefix = searchParam.getMimeType() == null ? "" : searchParam.getMimeType();
|
||||
int indexOfSlash = mimePrefix.indexOf("/");
|
||||
if (indexOfSlash >= 0) {
|
||||
mimePrefix = mimePrefix.substring(0, indexOfSlash);
|
||||
}
|
||||
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new FileTypeMimeSearchParams(mimePrefix, this.dataSourceId),
|
||||
mimePrefix,
|
||||
mimePrefix,
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileTypeMimeSearchParams o1, FileTypeMimeSearchParams o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getMimeType(), o2.getMimeType());
|
||||
public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType());
|
||||
}
|
||||
|
||||
static class FileMimePrefixNode extends TreeNode<FileTypeMimeSearchParams> {
|
||||
@@ -193,14 +226,30 @@ public class ViewsTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
// GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeMimeSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<FileTypeMimeSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeMimeSearchParams.class);
|
||||
|
||||
String prefixWithSlash = this.mimeTypePrefix + "/";
|
||||
if (originalTreeItem != null
|
||||
&& (originalTreeItem.getSearchParams().getMimeType().startsWith(prefixWithSlash))
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
FileTypeMimeSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
String mimeSuffix = searchParam.getMimeType().substring(prefixWithSlash.length());
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new FileTypeMimeSearchParams(searchParam.getMimeType(), this.dataSourceId),
|
||||
mimeSuffix,
|
||||
mimeSuffix,
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileTypeMimeSearchParams o1, FileTypeMimeSearchParams o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getMimeType(), o2.getMimeType());
|
||||
public int compare(TreeItemDTO<? extends FileTypeMimeSearchParams> o1, TreeItemDTO<? extends FileTypeMimeSearchParams> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getMimeType(), o2.getSearchParams().getMimeType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,14 +327,28 @@ public class ViewsTypeFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent daoEvt) {
|
||||
//GVDTODO
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends FileTypeExtensionsSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<FileTypeExtensionsSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, FileTypeExtensionsSearchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
&& this.childFilters.contains(originalTreeItem.getSearchParams().getFilter())
|
||||
&& (this.dataSourceId == null || Objects.equals(this.dataSourceId, originalTreeItem.getSearchParams().getDataSourceId()))) {
|
||||
|
||||
// generate new type so that if it is a subtree event (i.e. keyword hits), the right tree item is created.
|
||||
FileTypeExtensionsSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
AnalysisResultSearchParam.getTypeId(),
|
||||
new FileTypeExtensionsSearchParams(searchParam.getFilter(), this.dataSourceId),
|
||||
searchParam.getFilter(),
|
||||
searchParam.getFilter().getDisplayName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(FileTypeExtensionsSearchParams o1, FileTypeExtensionsSearchParams o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getFilter().getDisplayName(), o2.getFilter().getDisplayName());
|
||||
public int compare(TreeItemDTO<? extends FileTypeExtensionsSearchParams> o1, TreeItemDTO<? extends FileTypeExtensionsSearchParams> o2) {
|
||||
return STRING_COMPARATOR.compare(o1.getSearchParams().getFilter().getDisplayName(), o2.getSearchParams().getFilter().getDisplayName());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user