mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
Merge pull request #7813 from gdicristofaro/7072ntl2
7072 new_table_load score changes
This commit is contained in:
commit
73366ec442
@ -106,7 +106,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ReportsDAO.ReportsFetcher;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ReportsSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ViewsDAO.ScoreFileFetcher;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreDAO.ScoreContentFetcher;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.CacheClearEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.SearchManager;
|
||||
@ -1373,7 +1373,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C
|
||||
*/
|
||||
void displayScoreContent(ScoreViewSearchParams scoreSearchParams) {
|
||||
try {
|
||||
this.searchResultManager = new SearchManager(new ScoreFileFetcher(scoreSearchParams), getPageSize());
|
||||
this.searchResultManager = new SearchManager(new ScoreContentFetcher(scoreSearchParams), getPageSize());
|
||||
SearchResultsDTO results = searchResultManager.getResults();
|
||||
displaySearchResults(results, true);
|
||||
} catch (ExecutionException ex) {
|
||||
|
@ -150,15 +150,16 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
|
||||
return IGNORED_TYPES;
|
||||
}
|
||||
|
||||
TableData createTableData(BlackboardArtifact.Type artType, List<BlackboardArtifact> arts) throws TskCoreException, NoCurrentCaseException {
|
||||
TableData createTableData(BlackboardArtifact.Type artType, List<? extends BlackboardArtifact> arts) throws TskCoreException, NoCurrentCaseException {
|
||||
// A linked hashmap is being used for artifactAttributes to ensure that artifact order
|
||||
// as well as attribute orders within those artifacts are preserved. This is to maintain
|
||||
// a consistent ordering of attribute columns as received from BlackboardArtifact.getAttributes
|
||||
Map<Long, Map<BlackboardAttribute.Type, Object>> artifactAttributes = new LinkedHashMap<>();
|
||||
for (BlackboardArtifact art : arts) {
|
||||
BlackboardArtifact.Type thisArtType = artType != null ? artType : art.getType();
|
||||
Map<BlackboardAttribute.Type, Object> attrs = art.getAttributes().stream()
|
||||
.filter(attr -> isRenderedAttr(artType, attr.getAttributeType()))
|
||||
.collect(Collectors.toMap(attr -> attr.getAttributeType(), attr -> getAttrValue(artType, attr), (attr1, attr2) -> attr1, LinkedHashMap::new));
|
||||
.filter(attr -> isRenderedAttr(thisArtType, attr.getAttributeType()))
|
||||
.collect(Collectors.toMap(attr -> attr.getAttributeType(), attr -> getAttrValue(thisArtType, attr), (attr1, attr2) -> attr1, LinkedHashMap::new));
|
||||
|
||||
artifactAttributes.put(art.getId(), attrs);
|
||||
}
|
||||
@ -205,7 +206,8 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
|
||||
cellValues.add(dataSourceName);
|
||||
|
||||
AbstractFile linkedFile = null;
|
||||
if (artType.getCategory().equals(BlackboardArtifact.Category.DATA_ARTIFACT)) {
|
||||
BlackboardArtifact.Type thisArtType = artType != null ? artType : artifact.getType();
|
||||
if (thisArtType.getCategory().equals(BlackboardArtifact.Category.DATA_ARTIFACT)) {
|
||||
// Note that we need to get the attribute from the original artifact since it is not displayed.
|
||||
if (artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID) != null) {
|
||||
long linkedId = artifact.getAttribute(BlackboardAttribute.Type.TSK_PATH_ID).getValueLong();
|
||||
@ -469,7 +471,7 @@ abstract class BlackboardArtifactDAO extends AbstractDAO {
|
||||
return pagedArtsStream.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
class TableData {
|
||||
static class TableData {
|
||||
|
||||
final List<ColumnKey> columnKeys;
|
||||
final List<RowDTO> rows;
|
||||
|
@ -105,6 +105,13 @@ ReportsRowDTO_reportFilePath_displayName=Report File Path
|
||||
ReportsRowDTO_reportName_displayName=Report Name
|
||||
ReportsRowDTO_sourceModuleName_displayName=Source Module Name
|
||||
ResultTag.name.text=Result Tag
|
||||
ScoreDAO_columns_createdDateLbl=Created Date
|
||||
ScoreDAO_columns_noDescription=No Description
|
||||
ScoreDAO_columns_pathLbl=Path
|
||||
ScoreDAO_columns_sourceLbl=Source
|
||||
ScoreDAO_columns_typeLbl=Type
|
||||
ScoreDAO_mainNode_displayName=Score
|
||||
ScoreDAO_types_filelbl=File
|
||||
ScoreViewFilter_bad_name=Bad Items
|
||||
ScoreViewFilter_suspicious_name=Suspicious Items
|
||||
TagsDAO.fileColumns.accessTimeColLbl=Accessed Time
|
||||
|
@ -173,6 +173,7 @@ public class MainDAO extends AbstractDAO {
|
||||
private final EmailsDAO emailsDAO = EmailsDAO.getInstance();
|
||||
private final HostPersonDAO hostPersonDAO = HostPersonDAO.getInstance();
|
||||
private final ReportsDAO reportsDAO = ReportsDAO.getInstance();
|
||||
private final ScoreDAO scoreDAO = ScoreDAO.getInstance();
|
||||
|
||||
// NOTE: whenever adding a new sub-dao, it should be added to this list for event updates.
|
||||
private final List<AbstractDAO> allDAOs = ImmutableList.of(dataArtifactDAO,
|
||||
@ -185,7 +186,8 @@ public class MainDAO extends AbstractDAO {
|
||||
creditCardDAO,
|
||||
emailsDAO,
|
||||
hostPersonDAO,
|
||||
reportsDAO);
|
||||
reportsDAO,
|
||||
scoreDAO);
|
||||
|
||||
/**
|
||||
* Registers listeners with autopsy event publishers and starts internal
|
||||
@ -265,6 +267,10 @@ public class MainDAO extends AbstractDAO {
|
||||
return reportsDAO;
|
||||
}
|
||||
|
||||
public ScoreDAO getScoreDAO() {
|
||||
return scoreDAO;
|
||||
}
|
||||
|
||||
public PropertyChangeManager getResultEventsManager() {
|
||||
return this.resultEventsManager;
|
||||
}
|
||||
@ -316,10 +322,9 @@ public class MainDAO extends AbstractDAO {
|
||||
/**
|
||||
* Processes and handles an autopsy event.
|
||||
*
|
||||
* @param evt The event.
|
||||
* @param evt The event.
|
||||
* @param immediateResultAction If true, result events are immediately
|
||||
* fired. Otherwise, the result events are
|
||||
* batched.
|
||||
* fired. Otherwise, the result events are batched.
|
||||
*/
|
||||
private void handleEvent(PropertyChangeEvent evt, boolean immediateResultAction) {
|
||||
Collection<DAOEvent> daoEvts = processEvent(evt);
|
||||
|
632
Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreDAO.java
Normal file
632
Core/src/org/sleuthkit/autopsy/mainui/datamodel/ScoreDAO.java
Normal file
@ -0,0 +1,632 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2023 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.TimeZoneUtils;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactDAO.TableData;
|
||||
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.ScoreContentEvent;
|
||||
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;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.Category;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.DataArtifact;
|
||||
import org.sleuthkit.datamodel.Score;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
|
||||
/**
|
||||
* Provides information to populate the results viewer for data in the views
|
||||
* section.
|
||||
*/
|
||||
@Messages({
|
||||
"ScoreDAO_columns_sourceLbl=Source",
|
||||
"ScoreDAO_columns_typeLbl=Type",
|
||||
"ScoreDAO_columns_pathLbl=Path",
|
||||
"ScoreDAO_columns_createdDateLbl=Created Date",
|
||||
"ScoreDAO_columns_noDescription=No Description",
|
||||
"ScoreDAO_types_filelbl=File",
|
||||
"ScoreDAO_mainNode_displayName=Score"
|
||||
})
|
||||
public class ScoreDAO extends AbstractDAO {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ScoreDAO.class.getName());
|
||||
|
||||
private static final List<BlackboardAttribute.Type> TIME_ATTRS = Arrays.asList(
|
||||
BlackboardAttribute.Type.TSK_DATETIME,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_ACCESSED,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_RCVD,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_SENT,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_CREATED,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_MODIFIED,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_START,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_END,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_DELETED,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_RESET,
|
||||
BlackboardAttribute.Type.TSK_DATETIME_PASSWORD_FAIL
|
||||
);
|
||||
|
||||
private static final Map<Integer, Integer> TIME_ATTR_IMPORTANCE = IntStream.range(0, TIME_ATTRS.size())
|
||||
.mapToObj(idx -> Pair.of(TIME_ATTRS.get(idx).getTypeID(), idx))
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (v1, v2) -> v1));
|
||||
|
||||
private static final List<ColumnKey> RESULT_SCORE_COLUMNS = Arrays.asList(
|
||||
getFileColumnKey(Bundle.ScoreDAO_columns_sourceLbl()),
|
||||
getFileColumnKey(Bundle.ScoreDAO_columns_typeLbl()),
|
||||
getFileColumnKey(Bundle.ScoreDAO_columns_pathLbl()),
|
||||
getFileColumnKey(Bundle.ScoreDAO_columns_createdDateLbl())
|
||||
);
|
||||
|
||||
private static final String SCORE_TYPE_ID = ScoreDAO.class.getName() + "_SIGNATURE_ID";
|
||||
|
||||
private static final String BASE_AGGR_SCORE_QUERY
|
||||
= "FROM tsk_aggregate_score aggr_score\n"
|
||||
+ "INNER JOIN (\n"
|
||||
+ " SELECT obj_id, data_source_obj_id, 'f' AS type FROM tsk_files\n"
|
||||
+ " UNION SELECT artifact_obj_id AS obj_id, data_source_obj_id, 'a' AS type FROM blackboard_artifacts\n"
|
||||
+ " WHERE blackboard_artifacts.artifact_type_id IN\n"
|
||||
+ " (SELECT artifact_type_id FROM blackboard_artifact_types WHERE category_type = " + Category.DATA_ARTIFACT.getID() + ")\n"
|
||||
+ ") art_files ON aggr_score.obj_id = art_files.obj_id\n";
|
||||
|
||||
private static ScoreDAO instance = null;
|
||||
|
||||
synchronized static ScoreDAO getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ScoreDAO();
|
||||
}
|
||||
|
||||
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 static ColumnKey getFileColumnKey(String name) {
|
||||
return new ColumnKey(name, name, Bundle.ScoreDAO_columns_noDescription());
|
||||
}
|
||||
|
||||
private SleuthkitCase getCase() throws NoCurrentCaseException {
|
||||
return Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||
}
|
||||
|
||||
public SearchResultsDTO getFilesByScore(ScoreViewSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
|
||||
if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchScoreSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
private boolean isScoreContentInvalidating(ScoreViewSearchParams params, DAOEvent eventData) {
|
||||
if (!(eventData instanceof ScoreContentEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScoreContentEvent scoreContentEvt = (ScoreContentEvent) eventData;
|
||||
|
||||
ScoreViewFilter evtFilter = scoreContentEvt.getFilter();
|
||||
ScoreViewFilter paramsFilter = params.getFilter();
|
||||
|
||||
Long evtDsId = scoreContentEvt.getDataSourceId();
|
||||
Long paramsDsId = params.getDataSourceId();
|
||||
|
||||
return (evtFilter == null || evtFilter.equals(paramsFilter))
|
||||
&& (paramsDsId == null || evtDsId == null
|
||||
|| Objects.equals(paramsDsId, evtDsId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns counts for deleted content categories.
|
||||
*
|
||||
* @param dataSourceId The data source object id or null if no data source
|
||||
* filtering should occur.
|
||||
*
|
||||
* @return The results.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<ScoreViewSearchParams> getScoreContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
|
||||
Set<ScoreViewFilter> indeterminateFilters = new HashSet<>();
|
||||
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof ScoreContentEvent) {
|
||||
ScoreContentEvent scoreEvt = (ScoreContentEvent) evt;
|
||||
if (dataSourceId == null || scoreEvt.getDataSourceId() == null || Objects.equals(scoreEvt.getDataSourceId(), dataSourceId)) {
|
||||
if (scoreEvt.getFilter() == null) {
|
||||
// if null filter, indicates full refresh and all file sizes need refresh.
|
||||
indeterminateFilters.addAll(Arrays.asList(ScoreViewFilter.values()));
|
||||
break;
|
||||
} else {
|
||||
indeterminateFilters.add(scoreEvt.getFilter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String dsClause = getDsFilter(dataSourceId);
|
||||
|
||||
String queryStrSelects = Stream.of(ScoreViewFilter.values())
|
||||
.map((filter) -> Pair.of(filter.name(), getScoreFilter(filter.getScores())))
|
||||
.map((filterSqlPair) -> {
|
||||
String filterSql = Stream.of(filterSqlPair.getRight(), dsClause)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.joining("\nAND "));
|
||||
|
||||
if (StringUtils.isNotBlank(filterSql)) {
|
||||
filterSql = "\n WHERE " + filterSql;
|
||||
}
|
||||
|
||||
return "(SELECT COUNT(aggr_score.obj_id) "
|
||||
+ BASE_AGGR_SCORE_QUERY
|
||||
+ filterSql
|
||||
+ ") AS "
|
||||
+ filterSqlPair.getLeft();
|
||||
})
|
||||
.collect(Collectors.joining(",\n"));
|
||||
|
||||
String queryStr = "\n" + queryStrSelects;
|
||||
|
||||
try {
|
||||
SleuthkitCase skCase = getCase();
|
||||
|
||||
List<TreeItemDTO<ScoreViewSearchParams>> treeList = new ArrayList<>();
|
||||
skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> {
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
for (ScoreViewFilter filter : ScoreViewFilter.values()) {
|
||||
long count = resultSet.getLong(filter.name());
|
||||
TreeDisplayCount displayCount = indeterminateFilters.contains(filter)
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(count);
|
||||
|
||||
treeList.add(createScoreContentTreeItem(filter, dataSourceId, displayCount));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex);
|
||||
}
|
||||
});
|
||||
|
||||
return new TreeResultsDTO<>(treeList);
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeItemDTO<ScoreViewSearchParams> createScoreContentTreeItem(ScoreViewFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
"SCORE_CONTENT",
|
||||
new ScoreViewSearchParams(filter, dataSourceId),
|
||||
filter,
|
||||
filter == null ? "" : filter.getDisplayName(),
|
||||
displayCount);
|
||||
}
|
||||
|
||||
private static String getScoreFilter(Collection<Score> scores) {
|
||||
if (CollectionUtils.isEmpty(scores)) {
|
||||
return null;
|
||||
} else {
|
||||
return "("
|
||||
+ scores.stream()
|
||||
.map(s -> MessageFormat.format(
|
||||
" (aggr_score.significance = {0} AND aggr_score.priority = {1}) ",
|
||||
s.getSignificance().getId(),
|
||||
s.getPriority().getId()))
|
||||
.collect(Collectors.joining(" OR "))
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getDsFilter(Long dataSourceId) {
|
||||
return (dataSourceId == null || dataSourceId <= 0)
|
||||
? null
|
||||
: "aggr_score.data_source_obj_id = " + dataSourceId;
|
||||
}
|
||||
|
||||
private SearchResultsDTO fetchScoreSearchResultsDTOs(ScoreViewFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
|
||||
String scoreClause = getScoreFilter(filter.getScores());
|
||||
String dsClause = getDsFilter(dataSourceId);
|
||||
|
||||
String filterSql = Stream.of(scoreClause, dsClause)
|
||||
.filter(str -> str != null)
|
||||
.collect(Collectors.joining(" AND "));
|
||||
|
||||
filterSql = StringUtils.isNotEmpty(filterSql) ? " WHERE " + filterSql + "\n" : "";
|
||||
|
||||
String baseQuery = BASE_AGGR_SCORE_QUERY
|
||||
+ filterSql
|
||||
+ "ORDER BY art_files.obj_id";
|
||||
|
||||
String countQuery = " COUNT(art_files.obj_id) AS count\n" + baseQuery;
|
||||
|
||||
AtomicLong totalCountRef = new AtomicLong(0);
|
||||
AtomicReference<SQLException> countException = new AtomicReference<>(null);
|
||||
getCase().getCaseDbAccessManager()
|
||||
.select(countQuery, (rs) -> {
|
||||
try {
|
||||
if (rs.next()) {
|
||||
totalCountRef.set(rs.getLong("count"));
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
countException.set(ex);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
SQLException sqlEx = countException.get();
|
||||
if (sqlEx != null) {
|
||||
throw new TskCoreException(
|
||||
MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", countQuery),
|
||||
sqlEx);
|
||||
}
|
||||
|
||||
String objIdQuery = " art_files.obj_id, art_files.type\n" + baseQuery + "\n"
|
||||
+ (maxResultCount != null && maxResultCount > 0 ? " LIMIT " + maxResultCount : "")
|
||||
+ (startItem > 0 ? " OFFSET " + startItem : "");;
|
||||
|
||||
List<Long> fileIds = new ArrayList<>();
|
||||
List<Long> artifactIds = new ArrayList<>();
|
||||
AtomicReference<SQLException> objIdException = new AtomicReference<>(null);
|
||||
getCase().getCaseDbAccessManager()
|
||||
.select(objIdQuery, (rs) -> {
|
||||
try {
|
||||
while (rs.next()) {
|
||||
String type = rs.getString("type");
|
||||
if ("f".equalsIgnoreCase(type)) {
|
||||
fileIds.add(rs.getLong("obj_id"));
|
||||
} else {
|
||||
artifactIds.add(rs.getLong("obj_id"));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
objIdException.set(ex);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
sqlEx = objIdException.get();
|
||||
if (sqlEx != null) {
|
||||
throw new TskCoreException(
|
||||
MessageFormat.format("A sql exception occurred fetching results with query: SELECT {0}", objIdQuery),
|
||||
sqlEx);
|
||||
}
|
||||
|
||||
List<RowDTO> dataRows = new ArrayList<>();
|
||||
|
||||
if (!fileIds.isEmpty()) {
|
||||
String joinedFileIds = fileIds.stream()
|
||||
.map(l -> Long.toString(l))
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
List<AbstractFile> files = getCase().findAllFilesWhere("obj_id IN (" + joinedFileIds + ")");
|
||||
|
||||
for (AbstractFile file : files) {
|
||||
List<Object> cellValues = Arrays.asList(
|
||||
file.getName(),
|
||||
Bundle.ScoreDAO_types_filelbl(),
|
||||
file.getUniquePath(),
|
||||
file.getCtime() <= 0
|
||||
? null
|
||||
: TimeZoneUtils.getFormattedTime(file.getCtime())
|
||||
);
|
||||
|
||||
dataRows.add(new ScoreResultRowDTO(
|
||||
new FileRowDTO(
|
||||
file,
|
||||
file.getId(),
|
||||
file.getName(),
|
||||
file.getNameExtension(),
|
||||
MediaTypeUtils.getExtensionMediaType(file.getNameExtension()),
|
||||
file.isDirNameFlagSet(TSK_FS_NAME_FLAG_ENUM.ALLOC),
|
||||
file.getType(),
|
||||
cellValues),
|
||||
// the modified column types: source name, type, path, created time
|
||||
cellValues,
|
||||
file.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!artifactIds.isEmpty()) {
|
||||
String joinedArtifactIds = artifactIds.stream()
|
||||
.map(l -> Long.toString(l))
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
List<DataArtifact> dataArtifacts = getCase().getBlackboard().getDataArtifactsWhere("artifacts.artifact_obj_id IN (" + joinedArtifactIds + ")");
|
||||
TableData artTableData = MainDAO.getInstance().getDataArtifactsDAO().createTableData(null, dataArtifacts);
|
||||
|
||||
// all rows should be data artifact rows, and can be appended accordingly
|
||||
for (RowDTO rowDTO : artTableData.rows) {
|
||||
if (rowDTO instanceof DataArtifactRowDTO dataArtRow) {
|
||||
BlackboardArtifact.Type artifactType = dataArtRow.getArtifact().getType();
|
||||
List<Object> cellValues = Arrays.asList(
|
||||
dataArtRow.getSrcContent().getName(),
|
||||
artifactType.getDisplayName(),
|
||||
dataArtRow.getArtifact().getUniquePath(),
|
||||
getTimeStamp(dataArtRow.getArtifact())
|
||||
);
|
||||
|
||||
dataRows.add(new ScoreResultRowDTO(
|
||||
new DataArtifactRowDTO(
|
||||
dataArtRow.getArtifact(),
|
||||
dataArtRow.getSrcContent(),
|
||||
dataArtRow.getLinkedFile(),
|
||||
dataArtRow.isTimelineSupported(),
|
||||
cellValues,
|
||||
dataArtRow.getId()),
|
||||
artifactType,
|
||||
cellValues,
|
||||
dataArtRow.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return new BaseSearchResultsDTO(
|
||||
SCORE_TYPE_ID,
|
||||
Bundle.ScoreDAO_mainNode_displayName(),
|
||||
RESULT_SCORE_COLUMNS,
|
||||
dataRows,
|
||||
SCORE_TYPE_ID,
|
||||
startItem,
|
||||
totalCountRef.get());
|
||||
}
|
||||
|
||||
private String getTimeStamp(BlackboardArtifact artifact) {
|
||||
Long time = getTime(artifact);
|
||||
if (time == null || time <= 0) {
|
||||
return null;
|
||||
} else {
|
||||
return TimeZoneUtils.getFormattedTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
private Long getTime(BlackboardArtifact artifact) {
|
||||
try {
|
||||
BlackboardAttribute timeAttr = artifact.getAttributes().stream()
|
||||
.filter((attr) -> TIME_ATTR_IMPORTANCE.keySet().contains(attr.getAttributeType().getTypeID()))
|
||||
.sorted(Comparator.comparing(attr -> TIME_ATTR_IMPORTANCE.get(attr.getAttributeType().getTypeID())))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (timeAttr != null) {
|
||||
return timeAttr.getValueLong();
|
||||
} else {
|
||||
return (artifact.getParent() instanceof AbstractFile) ? ((AbstractFile) artifact.getParent()).getCtime() : null;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "An exception occurred while fetching time for artifact", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private TreeItemDTO<?> createTreeItem(DAOEvent daoEvent, TreeDisplayCount count) {
|
||||
|
||||
if (daoEvent instanceof ScoreContentEvent) {
|
||||
ScoreContentEvent scoreEvt = (ScoreContentEvent) daoEvent;
|
||||
return createScoreContentTreeItem(scoreEvt.getFilter(), scoreEvt.getDataSourceId(), count);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearCaches() {
|
||||
this.searchParamsCache.invalidateAll();
|
||||
handleIngestComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(searchParams) -> searchParamsMatchEvent(true, null, true, searchParams));
|
||||
|
||||
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
|
||||
(daoEvt, count) -> createTreeItem(daoEvt, count));
|
||||
|
||||
Set<? extends DAOEvent> fileViewRefreshEvents = getFileViewRefreshEvents(null);
|
||||
|
||||
List<? extends DAOEvent> fileViewRefreshTreeEvents = fileViewRefreshEvents.stream()
|
||||
.map(evt -> new TreeEvent(createTreeItem(evt, TreeDisplayCount.UNSPECIFIED), true))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Stream.of(treeEvts, fileViewRefreshEvents, fileViewRefreshTreeEvents)
|
||||
.flatMap(c -> c.stream())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<TreeEvent> shouldRefreshTree() {
|
||||
return SubDAOUtils.getRefreshEvents(this.treeCounts,
|
||||
(daoEvt, count) -> createTreeItem(daoEvt, count));
|
||||
}
|
||||
|
||||
@Override
|
||||
Set<DAOEvent> processEvent(PropertyChangeEvent evt) {
|
||||
AbstractFile af;
|
||||
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
|
||||
Long dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
|
||||
return invalidateScoreParamsAndReturnEvents(dsId);
|
||||
|
||||
} else if ((af = DAOEventUtils.getFileFromFileEvent(evt)) != null) {
|
||||
return invalidateScoreParamsAndReturnEvents(af.getDataSourceObjectId());
|
||||
}
|
||||
|
||||
ModuleDataEvent dataEvt;
|
||||
if (Case.Events.CONTENT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof ContentTagAddedEvent) && ((ContentTagAddedEvent) evt).getAddedTag().getContent() instanceof AbstractFile) {
|
||||
ContentTagAddedEvent tagAddedEvt = (ContentTagAddedEvent) evt;
|
||||
return invalidateScoreParamsAndReturnEvents(((AbstractFile) tagAddedEvt.getAddedTag().getContent()).getDataSourceObjectId());
|
||||
} else if (Case.Events.CONTENT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
|
||||
return invalidateScoreParamsAndReturnEvents(null);
|
||||
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof BlackBoardArtifactTagAddedEvent)) {
|
||||
BlackBoardArtifactTagAddedEvent artifactAddedEvt = (BlackBoardArtifactTagAddedEvent) evt;
|
||||
return invalidateScoreParamsAndReturnEvents(artifactAddedEvt.getAddedTag().getArtifact().getDataSourceObjectID());
|
||||
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
|
||||
return invalidateScoreParamsAndReturnEvents(null);
|
||||
} else if ((dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt)) != null
|
||||
&& Category.ANALYSIS_RESULT.equals(dataEvt.getBlackboardArtifactType().getCategory())) {
|
||||
Set<Long> dsIds = dataEvt.getArtifacts().stream().map(ar -> ar.getDataSourceObjectID()).distinct().collect(Collectors.toSet());
|
||||
return invalidateScoreParamsAndReturnEventsFromSet(dsIds);
|
||||
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<DAOEvent> invalidateScoreParamsAndReturnEvents(Long dataSourceId) {
|
||||
return invalidateScoreParamsAndReturnEventsFromSet(dataSourceId != null ? Collections.singleton(dataSourceId) : null);
|
||||
}
|
||||
|
||||
private Set<DAOEvent> invalidateScoreParamsAndReturnEventsFromSet(Set<Long> dataSourceIds) {
|
||||
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(searchParams) -> {
|
||||
if (searchParams instanceof ScoreViewSearchParams) {
|
||||
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
|
||||
return (CollectionUtils.isEmpty(dataSourceIds) || scoreParams.getDataSourceId() == null || dataSourceIds.contains(scoreParams.getDataSourceId()));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Set<DAOEvent> dataEvents = CollectionUtils.isEmpty(dataSourceIds)
|
||||
? Collections.singleton(new ScoreContentEvent(null, null))
|
||||
: dataSourceIds.stream().map(dsId -> new ScoreContentEvent(null, dsId)).collect(Collectors.toSet());
|
||||
|
||||
Set<DAOEvent> treeEvents = CollectionUtils.isEmpty(dataSourceIds)
|
||||
? Collections.singleton(new TreeEvent(createScoreContentTreeItem(
|
||||
null,
|
||||
null,
|
||||
TreeDisplayCount.UNSPECIFIED),
|
||||
true))
|
||||
: dataSourceIds.stream().map(dsId -> new TreeEvent(
|
||||
createScoreContentTreeItem(
|
||||
null,
|
||||
dsId,
|
||||
TreeDisplayCount.UNSPECIFIED), true))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return Stream.of(dataEvents, treeEvents).flatMap(s -> s.stream()).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean searchParamsMatchEvent(
|
||||
boolean invalidatesScore,
|
||||
Long dsId,
|
||||
boolean dataSourceAdded,
|
||||
Object searchParams) {
|
||||
|
||||
if (searchParams instanceof ScoreViewSearchParams) {
|
||||
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
|
||||
return (dataSourceAdded || (invalidatesScore && (scoreParams.getDataSourceId() == null || dsId == null || Objects.equals(scoreParams.getDataSourceId(), dsId))));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns events for when a full refresh is required because module content
|
||||
* events will not necessarily provide events for files (i.e. data source
|
||||
* added, ingest cancelled/completed).
|
||||
*
|
||||
* @param dataSourceId The data source id or null if not applicable.
|
||||
*
|
||||
* @return The set of events that apply in this situation.
|
||||
*/
|
||||
private Set<DAOEvent> getFileViewRefreshEvents(Long dataSourceId) {
|
||||
return ImmutableSet.of(
|
||||
new ScoreContentEvent(null, dataSourceId)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for deleted content.
|
||||
*/
|
||||
public static class ScoreContentFetcher extends DAOFetcher<ScoreViewSearchParams> {
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param params Parameters to handle fetching of data.
|
||||
*/
|
||||
public ScoreContentFetcher(ScoreViewSearchParams params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
protected ScoreDAO getDAO() {
|
||||
return MainDAO.getInstance().getScoreDAO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException {
|
||||
return getDAO().getFilesByScore(this.getParameters(), pageIdx * pageSize, (long) pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(DAOEvent evt) {
|
||||
return getDAO().isScoreContentInvalidating(this.getParameters(), evt);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2023 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.datamodel;
|
||||
|
||||
import java.util.List;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
/**
|
||||
* Score results dto.
|
||||
*/
|
||||
public class ScoreResultRowDTO extends BaseRowDTO {
|
||||
private static final String TYPE_ID = "SCORE_RESULT_ROW";
|
||||
|
||||
public static String getTypeIdForClass() {
|
||||
return TYPE_ID;
|
||||
}
|
||||
|
||||
|
||||
private final FileRowDTO fileDTO;
|
||||
private final DataArtifactRowDTO artifactDTO;
|
||||
private final BlackboardArtifact.Type artifactType;
|
||||
|
||||
|
||||
public ScoreResultRowDTO(FileRowDTO fileDTO, List<Object> cellValues, long id) {
|
||||
super(cellValues, TYPE_ID, id);
|
||||
this.fileDTO = fileDTO;
|
||||
this.artifactDTO = null;
|
||||
this.artifactType = null;
|
||||
}
|
||||
|
||||
public ScoreResultRowDTO(DataArtifactRowDTO artifactDTO, BlackboardArtifact.Type artifactType, List<Object> cellValues, long id) {
|
||||
super(cellValues, TYPE_ID, id);
|
||||
this.fileDTO = null;
|
||||
this.artifactDTO = artifactDTO;
|
||||
this.artifactType = artifactType;
|
||||
}
|
||||
|
||||
public FileRowDTO getFileDTO() {
|
||||
return fileDTO;
|
||||
}
|
||||
|
||||
public DataArtifactRowDTO getArtifactDTO() {
|
||||
return artifactDTO;
|
||||
}
|
||||
|
||||
public BlackboardArtifact.Type getArtifactType() {
|
||||
return artifactType;
|
||||
}
|
||||
}
|
@ -42,19 +42,13 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import static org.sleuthkit.autopsy.core.UserPreferences.hideKnownFilesInViewsTree;
|
||||
import static org.sleuthkit.autopsy.core.UserPreferences.hideSlackFilesInViewsTree;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_DURATION_UNITS;
|
||||
import static org.sleuthkit.autopsy.mainui.datamodel.AbstractDAO.CACHE_SIZE;
|
||||
@ -65,12 +59,10 @@ 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.ScoreContentEvent;
|
||||
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;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.Category;
|
||||
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbPreparedStatement;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -154,15 +146,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchSizeSearchResultsDTOs(key.getSizeFilter(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
public SearchResultsDTO getFilesByScore(ScoreViewSearchParams key, long startItem, Long maxCount) throws ExecutionException, IllegalArgumentException {
|
||||
if (key.getDataSourceId() != null && key.getDataSourceId() <= 0) {
|
||||
throw new IllegalArgumentException("Data source id must be greater than 0 or null");
|
||||
}
|
||||
|
||||
SearchParams<Object> searchParams = new SearchParams<>(key, startItem, maxCount);
|
||||
return searchParamsCache.get(searchParams, () -> fetchScoreSearchResultsDTOs(key.getFilter(), key.getDataSourceId(), startItem, maxCount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns search results for the given deleted content search params.
|
||||
@ -227,24 +210,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
&& (params.getDataSourceId() == null || deletedContentEvt.getDataSourceId() == null
|
||||
|| Objects.equals(params.getDataSourceId(), deletedContentEvt.getDataSourceId()));
|
||||
}
|
||||
|
||||
private boolean isScoreContentInvalidating(ScoreViewSearchParams params, DAOEvent eventData) {
|
||||
if (!(eventData instanceof ScoreContentEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScoreContentEvent scoreContentEvt = (ScoreContentEvent) eventData;
|
||||
|
||||
ScoreViewFilter evtFilter = scoreContentEvt.getFilter();
|
||||
ScoreViewFilter paramsFilter = params.getFilter();
|
||||
|
||||
Long evtDsId = scoreContentEvt.getDataSourceId();
|
||||
Long paramsDsId = params.getDataSourceId();
|
||||
|
||||
return (evtFilter == null || evtFilter.equals(paramsFilter))
|
||||
&& (paramsDsId == null || evtDsId == null
|
||||
|| Objects.equals(paramsDsId, evtDsId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sql 'and' clause to filter by data source id if one is present.
|
||||
@ -457,31 +422,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clause to be proceeded with 'where' or 'and' that will show
|
||||
* files specified by the filter and the specified data source.
|
||||
*
|
||||
* @param filter The scores filter.
|
||||
* @param dataSourceId The id of the data source or null if no data source
|
||||
* filtering.
|
||||
*
|
||||
* @return The clause to be proceeded with 'where' or 'and'.
|
||||
*/
|
||||
private String getFileScoreWhereStatement(ScoreViewFilter filter, Long dataSourceId) {
|
||||
String scoreWhereClause = filter.getScores().stream()
|
||||
.map(s -> MessageFormat.format(
|
||||
" (tsk_aggregate_score.significance = {0} AND tsk_aggregate_score.priority = {1}) ",
|
||||
s.getSignificance().getId(),
|
||||
s.getPriority().getId()))
|
||||
.collect(Collectors.joining(" OR "));
|
||||
String query = MessageFormat.format(
|
||||
" obj_id IN (SELECT tsk_aggregate_score.obj_id FROM tsk_aggregate_score WHERE {0}) {1}",
|
||||
scoreWhereClause,
|
||||
getDataSourceAndClause(dataSourceId));
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns counts for a collection of file extension search filters.
|
||||
@ -694,70 +634,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns counts for deleted content categories.
|
||||
*
|
||||
* @param dataSourceId The data source object id or null if no data source
|
||||
* filtering should occur.
|
||||
*
|
||||
* @return The results.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
public TreeResultsDTO<ScoreViewSearchParams> getScoreContentCounts(Long dataSourceId) throws IllegalArgumentException, ExecutionException {
|
||||
Set<ScoreViewFilter> indeterminateFilters = new HashSet<>();
|
||||
for (DAOEvent evt : this.treeCounts.getEnqueued()) {
|
||||
if (evt instanceof ScoreContentEvent) {
|
||||
ScoreContentEvent scoreEvt = (ScoreContentEvent) evt;
|
||||
if (dataSourceId == null || scoreEvt.getDataSourceId() == null || Objects.equals(scoreEvt.getDataSourceId(), dataSourceId)) {
|
||||
if (scoreEvt.getFilter() == null) {
|
||||
// if null filter, indicates full refresh and all file sizes need refresh.
|
||||
indeterminateFilters.addAll(Arrays.asList(ScoreViewFilter.values()));
|
||||
break;
|
||||
} else {
|
||||
indeterminateFilters.add(scoreEvt.getFilter());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String queryStr = Stream.of(ScoreViewFilter.values())
|
||||
.map((filter) -> {
|
||||
String clause = getFileScoreWhereStatement(filter, dataSourceId);
|
||||
return MessageFormat.format(" (SELECT COUNT(*) FROM tsk_files WHERE {0}) AS {1}", clause, filter.name());
|
||||
})
|
||||
.collect(Collectors.joining(", \n"));
|
||||
|
||||
try {
|
||||
SleuthkitCase skCase = getCase();
|
||||
|
||||
List<TreeItemDTO<ScoreViewSearchParams>> treeList = new ArrayList<>();
|
||||
skCase.getCaseDbAccessManager().select(queryStr, (resultSet) -> {
|
||||
try {
|
||||
if (resultSet.next()) {
|
||||
for (ScoreViewFilter filter : ScoreViewFilter.values()) {
|
||||
long count = resultSet.getLong(filter.name());
|
||||
TreeDisplayCount displayCount = indeterminateFilters.contains(filter)
|
||||
? TreeDisplayCount.INDETERMINATE
|
||||
: TreeDisplayCount.getDeterminate(count);
|
||||
|
||||
treeList.add(createScoreContentTreeItem(filter, dataSourceId, displayCount));
|
||||
}
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "An error occurred while fetching file type counts.", ex);
|
||||
}
|
||||
});
|
||||
|
||||
return new TreeResultsDTO<>(treeList);
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
throw new ExecutionException("An error occurred while fetching file counts with query:\n" + queryStr, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static TreeItemDTO<DeletedContentSearchParams> createDeletedContentTreeItem(DeletedContentFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
@ -767,16 +643,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
filter == null ? "" : filter.getDisplayName(),
|
||||
displayCount);
|
||||
}
|
||||
|
||||
|
||||
private static TreeItemDTO<ScoreViewSearchParams> createScoreContentTreeItem(ScoreViewFilter filter, Long dataSourceId, TreeDisplayCount displayCount) {
|
||||
return new TreeItemDTO<>(
|
||||
"SCORE_CONTENT",
|
||||
new ScoreViewSearchParams(filter, dataSourceId),
|
||||
filter,
|
||||
filter == null ? "" : filter.getDisplayName(),
|
||||
displayCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a size tree item.
|
||||
@ -1088,11 +954,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
String whereStatement = getFileSizesWhereStatement(filter, dataSourceId);
|
||||
return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount);
|
||||
}
|
||||
|
||||
private SearchResultsDTO fetchScoreSearchResultsDTOs(ScoreViewFilter filter, Long dataSourceId, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
|
||||
String whereStatement = getFileScoreWhereStatement(filter, dataSourceId);
|
||||
return fetchFileViewFiles(whereStatement, filter.getDisplayName(), startItem, maxResultCount);
|
||||
}
|
||||
|
||||
private SearchResultsDTO fetchFileViewFiles(String originalWhereStatement, String displayName, long startItem, Long maxResultCount) throws NoCurrentCaseException, TskCoreException {
|
||||
|
||||
@ -1156,9 +1017,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
} else if (daoEvent instanceof DeletedContentEvent) {
|
||||
DeletedContentEvent sizeEvt = (DeletedContentEvent) daoEvent;
|
||||
return createDeletedContentTreeItem(sizeEvt.getFilter(), sizeEvt.getDataSourceId(), count);
|
||||
} else if (daoEvent instanceof ScoreContentEvent) {
|
||||
ScoreContentEvent scoreEvt = (ScoreContentEvent) daoEvent;
|
||||
return createScoreContentTreeItem(scoreEvt.getFilter(), scoreEvt.getDataSourceId(), count);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -1174,7 +1032,7 @@ public class ViewsDAO extends AbstractDAO {
|
||||
@Override
|
||||
Set<? extends DAOEvent> handleIngestComplete() {
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(searchParams) -> searchParamsMatchEvent(null, null, null, null, true, null, true, searchParams));
|
||||
(searchParams) -> searchParamsMatchEvent(null, null, null, null, null, true, searchParams));
|
||||
|
||||
Set<? extends DAOEvent> treeEvts = SubDAOUtils.getIngestCompleteEvents(this.treeCounts,
|
||||
(daoEvt, count) -> createTreeItem(daoEvt, count));
|
||||
@ -1204,16 +1062,12 @@ public class ViewsDAO extends AbstractDAO {
|
||||
Set<DeletedContentFilter> deletedContentFilters = null;
|
||||
String evtMimeType = null;
|
||||
FileSizeFilter evtFileSize = null;
|
||||
boolean scoreInvalidating = false;
|
||||
|
||||
AbstractFile af;
|
||||
|
||||
if (Case.Events.DATA_SOURCE_ADDED.toString().equals(evt.getPropertyName())) {
|
||||
dsId = evt.getNewValue() instanceof Long ? (Long) evt.getNewValue() : null;
|
||||
dataSourceAdded = true;
|
||||
scoreInvalidating = true;
|
||||
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, scoreInvalidating, dsId, dataSourceAdded);
|
||||
} else if ((af = DAOEventUtils.getFileFromFileEvent(evt)) != null) {
|
||||
} else {
|
||||
AbstractFile af = DAOEventUtils.getFileFromFileEvent(evt);
|
||||
if (af == null) {
|
||||
return Collections.emptySet();
|
||||
} else if (hideKnownFilesInViewsTree() && TskData.FileKnown.KNOWN.equals(af.getKnown())) {
|
||||
@ -1245,31 +1099,9 @@ public class ViewsDAO extends AbstractDAO {
|
||||
if (evtExtFilters == null || evtExtFilters.isEmpty() && deletedContentFilters.isEmpty() && evtMimeType == null && evtFileSize == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// a file is being added, so invalidate all score items where data source id is in scope
|
||||
scoreInvalidating = true;
|
||||
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, scoreInvalidating, dsId, dataSourceAdded);
|
||||
}
|
||||
|
||||
ModuleDataEvent dataEvt;
|
||||
if (Case.Events.CONTENT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof ContentTagAddedEvent) && ((ContentTagAddedEvent) evt).getAddedTag().getContent() instanceof AbstractFile) {
|
||||
ContentTagAddedEvent tagAddedEvt = (ContentTagAddedEvent) evt;
|
||||
return invalidateScoreParamsAndReturnEvents(((AbstractFile) tagAddedEvt.getAddedTag().getContent()).getDataSourceObjectId());
|
||||
} else if (Case.Events.CONTENT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
|
||||
return invalidateScoreParamsAndReturnEvents(null);
|
||||
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString().equals(evt.getPropertyName()) && (evt instanceof BlackBoardArtifactTagAddedEvent)) {
|
||||
BlackBoardArtifactTagAddedEvent artifactAddedEvt = (BlackBoardArtifactTagAddedEvent) evt;
|
||||
return invalidateScoreParamsAndReturnEvents(artifactAddedEvt.getAddedTag().getArtifact().getDataSourceObjectID());
|
||||
} else if (Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString().equals(evt.getPropertyName())) {
|
||||
return invalidateScoreParamsAndReturnEvents(null);
|
||||
} else if ((dataEvt = DAOEventUtils.getModuelDataFromArtifactEvent(evt)) != null
|
||||
&& Category.ANALYSIS_RESULT.equals(dataEvt.getBlackboardArtifactType().getCategory())) {
|
||||
Set<Long> dsIds = dataEvt.getArtifacts().stream().map(ar -> ar.getDataSourceObjectID()).distinct().collect(Collectors.toSet());
|
||||
return invalidateScoreParamsAndReturnEventsFromSet(dsIds);
|
||||
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return invalidateAndReturnEvents(evtExtFilters, evtMimeType, evtFileSize, deletedContentFilters, dsId, dataSourceAdded);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1279,7 +1111,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @param evtMimeType The mime type or null.
|
||||
* @param evtFileSize The file size filter or null.
|
||||
* @param deletedContentFilters The set of affected deleted content filters.
|
||||
* @param invalidateScore All score views should be invalidated if data source id is in scope.
|
||||
* @param dsId The data source id or null.
|
||||
* @param dataSourceAdded Whether or not this is a data source added
|
||||
* event.
|
||||
@ -1287,42 +1118,19 @@ public class ViewsDAO extends AbstractDAO {
|
||||
* @return The set of dao events to be fired.
|
||||
*/
|
||||
private Set<DAOEvent> invalidateAndReturnEvents(Set<FileExtSearchFilter> evtExtFilters, String evtMimeType,
|
||||
FileSizeFilter evtFileSize, Set<DeletedContentFilter> deletedContentFilters, boolean invalidateScore, Long dsId, boolean dataSourceAdded) {
|
||||
FileSizeFilter evtFileSize, Set<DeletedContentFilter> deletedContentFilters, Long dsId, boolean dataSourceAdded) {
|
||||
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(searchParams) -> searchParamsMatchEvent(evtExtFilters, deletedContentFilters,
|
||||
evtMimeType, evtFileSize, invalidateScore, dsId, dataSourceAdded, searchParams));
|
||||
evtMimeType, evtFileSize, dsId, dataSourceAdded, searchParams));
|
||||
|
||||
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, invalidateScore, dsId, dataSourceAdded);
|
||||
}
|
||||
|
||||
private Set<DAOEvent> invalidateScoreParamsAndReturnEvents(Long dataSourceId) {
|
||||
return invalidateScoreParamsAndReturnEventsFromSet(dataSourceId != null ? Collections.singleton(dataSourceId) : null);
|
||||
}
|
||||
|
||||
|
||||
private Set<DAOEvent> invalidateScoreParamsAndReturnEventsFromSet(Set<Long> dataSourceIds) {
|
||||
|
||||
SubDAOUtils.invalidateKeys(this.searchParamsCache,
|
||||
(searchParams) -> {
|
||||
if (searchParams instanceof ScoreViewSearchParams) {
|
||||
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
|
||||
return (CollectionUtils.isEmpty(dataSourceIds) || scoreParams.getDataSourceId() == null || dataSourceIds.contains(scoreParams.getDataSourceId()));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return CollectionUtils.isEmpty(dataSourceIds)
|
||||
? Collections.singleton(new ScoreContentEvent(null, null))
|
||||
: dataSourceIds.stream().map(dsId -> new ScoreContentEvent(null, dsId)).collect(Collectors.toSet());
|
||||
return getDAOEvents(evtExtFilters, deletedContentFilters, evtMimeType, evtFileSize, dsId, dataSourceAdded);
|
||||
}
|
||||
|
||||
private boolean searchParamsMatchEvent(Set<FileExtSearchFilter> evtExtFilters,
|
||||
Set<DeletedContentFilter> deletedContentFilters,
|
||||
String evtMimeType,
|
||||
FileSizeFilter evtFileSize,
|
||||
boolean invalidatesScore,
|
||||
Long dsId,
|
||||
boolean dataSourceAdded,
|
||||
Object searchParams) {
|
||||
@ -1349,9 +1157,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
DeletedContentSearchParams deletedParams = (DeletedContentSearchParams) searchParams;
|
||||
return (dataSourceAdded || (deletedContentFilters != null && deletedContentFilters.contains(deletedParams.getFilter())))
|
||||
&& (deletedParams.getDataSourceId() == null || dsId == null || Objects.equals(deletedParams.getDataSourceId(), dsId));
|
||||
} else if (searchParams instanceof ScoreViewSearchParams) {
|
||||
ScoreViewSearchParams scoreParams = (ScoreViewSearchParams) searchParams;
|
||||
return (dataSourceAdded || (invalidatesScore && (scoreParams.getDataSourceId() == null || dsId == null || Objects.equals(scoreParams.getDataSourceId(), dsId))));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -1374,7 +1179,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
Set<DeletedContentFilter> deletedContentFilters,
|
||||
String mimeType,
|
||||
FileSizeFilter sizeFilter,
|
||||
boolean invalidateScore,
|
||||
Long dsId,
|
||||
boolean dataSourceAdded) {
|
||||
|
||||
@ -1398,10 +1202,6 @@ public class ViewsDAO extends AbstractDAO {
|
||||
if (sizeFilter != null) {
|
||||
daoEvents.add(new FileTypeSizeEvent(sizeFilter, dsId));
|
||||
}
|
||||
|
||||
if (invalidateScore) {
|
||||
daoEvents.add(new ScoreContentEvent(null, dsId));
|
||||
}
|
||||
|
||||
List<TreeEvent> treeEvents = this.treeCounts.enqueueAll(daoEvents).stream()
|
||||
.map(daoEvt -> new TreeEvent(createTreeItem(daoEvt, TreeDisplayCount.INDETERMINATE), false))
|
||||
@ -1456,13 +1256,10 @@ public class ViewsDAO extends AbstractDAO {
|
||||
return ImmutableSet.of(
|
||||
new DeletedContentEvent(null, dataSourceId),
|
||||
new FileTypeSizeEvent(null, dataSourceId),
|
||||
new FileTypeExtensionsEvent(null, dataSourceId),
|
||||
new ScoreContentEvent(null, dataSourceId)
|
||||
new FileTypeExtensionsEvent(null, dataSourceId)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for file types by extension.
|
||||
*/
|
||||
@ -1579,35 +1376,4 @@ public class ViewsDAO extends AbstractDAO {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles fetching and paging of data for deleted content.
|
||||
*/
|
||||
public static class ScoreFileFetcher extends DAOFetcher<ScoreViewSearchParams> {
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param params Parameters to handle fetching of data.
|
||||
*/
|
||||
public ScoreFileFetcher(ScoreViewSearchParams params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
protected ViewsDAO getDAO() {
|
||||
return MainDAO.getInstance().getViewsDAO();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResultsDTO getSearchResults(int pageSize, int pageIdx) throws ExecutionException {
|
||||
return getDAO().getFilesByScore(this.getParameters(), pageIdx * pageSize, (long) pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRefreshRequired(DAOEvent evt) {
|
||||
return getDAO().isScoreContentInvalidating(this.getParameters(), evt);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ RootFactory_OsAccountsRootNode_displayName=OS Accounts
|
||||
RootFactory_ReportsRootNode_displayName=Reports
|
||||
RootFactory_TagsRootNode_displayName=Tags
|
||||
RootFactory_ViewsRootNode_displayName=Views
|
||||
ScoreTypeFactory_ScoreParentNode_displayName=Score
|
||||
SearchResultRootNode_createSheet_childCount_displayName=Child Count
|
||||
SearchResultRootNode_createSheet_childCount_name=Child Count
|
||||
SearchResultRootNode_createSheet_type_displayName=Name
|
||||
@ -31,6 +32,5 @@ ViewsTypeFactory_DeletedParentNode_displayName=Deleted Files
|
||||
ViewsTypeFactory_ExtensionParentNode_displayName=By Extension
|
||||
ViewsTypeFactory_FileTypesParentNode_displayName=File Types
|
||||
ViewsTypeFactory_MimeParentNode_displayName=By MIME Type
|
||||
ViewsTypeFactory_ScoreParentNode_displayName=Score
|
||||
ViewsTypeFactory_SizeParentNode_displayName=File Size
|
||||
VolumnNode_ExtractUnallocAction_text=Extract Unallocated Space to Single Files
|
||||
|
@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.DataArtifactTableSearchResultsDTO.CommAccoutTableSearchResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.SearchResultsDTO;
|
||||
import org.sleuthkit.datamodel.DataArtifact;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -57,10 +58,10 @@ public class DataArtifactNode extends ArtifactNode<DataArtifact, DataArtifactRow
|
||||
this(tableData, artifactRow, getIconFilePath(tableData), backgroundTasksPool);
|
||||
}
|
||||
|
||||
public DataArtifactNode(DataArtifactTableSearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath, ExecutorService backgroundTasksPool) {
|
||||
public DataArtifactNode(SearchResultsDTO tableData, DataArtifactRowDTO artifactRow, String iconPath, ExecutorService backgroundTasksPool) {
|
||||
super(tableData, artifactRow, tableData.getColumns(), createLookup(artifactRow), iconPath, backgroundTasksPool);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Optional<List<Tag>> getAllTagsFromDatabase() {
|
||||
try {
|
||||
|
@ -50,6 +50,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.HostPersonEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.ScoreTypeFactory.ScoreParentNode;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.Host;
|
||||
@ -194,6 +195,7 @@ public class RootFactory {
|
||||
new AnalysisResultsRootNode(null),
|
||||
new OsAccountsRootNode(null),
|
||||
new TagsRootNode(null),
|
||||
new ScoreParentNode(null),
|
||||
new ReportsRootNode()
|
||||
));
|
||||
}
|
||||
@ -505,6 +507,7 @@ public class RootFactory {
|
||||
new DataArtifactsRootNode(dataSourceObjId),
|
||||
new AnalysisResultsRootNode(dataSourceObjId),
|
||||
new OsAccountsRootNode(dataSourceObjId),
|
||||
new ScoreParentNode(dataSourceObjId),
|
||||
new TagsRootNode(dataSourceObjId)
|
||||
));
|
||||
}
|
||||
@ -721,7 +724,7 @@ public class RootFactory {
|
||||
new TagNameFactory(dataSourceObjId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Root node for reports in the tree.
|
||||
*/
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2023 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.mainui.nodes;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode;
|
||||
|
||||
/**
|
||||
*
|
||||
* Factories for displaying views.
|
||||
*/
|
||||
public class ScoreTypeFactory {
|
||||
private static final String SCORE_ICON = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
|
||||
/**
|
||||
* Children of 'Score' in the tree.
|
||||
*/
|
||||
public static class ScoreChildren extends Children.Array {
|
||||
|
||||
public ScoreChildren(Long dataSourceId) {
|
||||
super(ImmutableList.of(
|
||||
new ScoreParentNode(dataSourceId)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent of score nodes in the tree.
|
||||
*/
|
||||
@Messages({"ScoreTypeFactory_ScoreParentNode_displayName=Score"})
|
||||
public static class ScoreParentNode extends StaticTreeNode {
|
||||
|
||||
ScoreParentNode(Long dataSourceId) {
|
||||
super(
|
||||
"FILE_VIEW_SCORE_PARENT",
|
||||
Bundle.ScoreTypeFactory_ScoreParentNode_displayName(),
|
||||
SCORE_ICON,
|
||||
new ScoreContentFactory(dataSourceId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory for creating deleted content tree nodes.
|
||||
*/
|
||||
public static class ScoreContentFactory extends TreeChildFactory<ScoreViewSearchParams> {
|
||||
|
||||
private final Long dataSourceId;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param dataSourceId The data source to filter files to or null.
|
||||
*/
|
||||
public ScoreContentFactory(Long dataSourceId) {
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeNode<ScoreViewSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> rowData) {
|
||||
return new ScoreContentTypeNode(rowData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO<? extends ScoreViewSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException {
|
||||
return MainDAO.getInstance().getScoreDAO().getScoreContentCounts(dataSourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
|
||||
for (DAOEvent evt : aggEvt.getEvents()) {
|
||||
if (evt instanceof TreeEvent) {
|
||||
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, ScoreViewSearchParams.class);
|
||||
// if search params has null filter, trigger full refresh
|
||||
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
|
||||
super.update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.handleDAOAggregateEvent(aggEvt);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, ScoreViewSearchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
// only create child if size filter is present (if null, update should be triggered separately)
|
||||
&& originalTreeItem.getSearchParams().getFilter() != 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.
|
||||
ScoreViewSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
ScoreViewSearchParams.getTypeId(),
|
||||
new ScoreViewSearchParams(searchParam.getFilter(), this.dataSourceId),
|
||||
searchParam.getFilter(),
|
||||
searchParam.getFilter().getDisplayName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(TreeItemDTO<? extends ScoreViewSearchParams> o1, TreeItemDTO<? extends ScoreViewSearchParams> o2) {
|
||||
return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a deleted content tree node.
|
||||
*/
|
||||
static class ScoreContentTypeNode extends TreeNode<ScoreViewSearchParams> {
|
||||
|
||||
private static final String BAD_SCORE_ICON = SCORE_ICON;
|
||||
private static final String SUS_SCORE_ICON = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
|
||||
|
||||
private static String getIcon(ScoreViewFilter filter) {
|
||||
switch (filter) {
|
||||
case SUSPICIOUS:
|
||||
return SUS_SCORE_ICON;
|
||||
case BAD:
|
||||
default:
|
||||
return BAD_SCORE_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param itemData The data for the node.
|
||||
*/
|
||||
ScoreContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> itemData) {
|
||||
super("SCORE_CONTENT_" + itemData.getSearchParams().getFilter().name(), getIcon(itemData.getSearchParams().getFilter()), itemData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondSelection(DataResultTopComponent dataResultPanel) {
|
||||
dataResultPanel.displayScoreContent(this.getItemData().getSearchParams());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ import java.util.stream.Collectors;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.utils.IconsUtil;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.AnalysisResultTableSearchResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.BlackboardArtifactTagsRowDTO;
|
||||
@ -49,6 +50,7 @@ import org.sleuthkit.autopsy.mainui.datamodel.RowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ContentRowDTO.VolumeRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.CreditCardByFileRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ReportsRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreResultRowDTO;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.FileNode.LayoutFileNode;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.FileNode.SlackFileNode;
|
||||
import org.sleuthkit.autopsy.mainui.nodes.SpecialDirectoryNode.LocalDirectoryNode;
|
||||
@ -89,7 +91,14 @@ public class SearchResultChildFactory extends ChildFactory<ChildKey> {
|
||||
protected Node createNodeForKey(ChildKey key) {
|
||||
String typeId = key.getRow().getTypeId();
|
||||
try {
|
||||
if (DataArtifactRowDTO.getTypeIdForClass().equals(typeId)) {
|
||||
if (ScoreResultRowDTO.getTypeIdForClass().equals(typeId) && key.getRow() instanceof ScoreResultRowDTO scoreRow) {
|
||||
if (scoreRow.getArtifactDTO() != null && scoreRow.getArtifactType() != null) {
|
||||
String iconPath = IconsUtil.getIconFilePath(scoreRow.getArtifactType().getTypeID());
|
||||
return new DataArtifactNode(key.getSearchResults(), scoreRow.getArtifactDTO(), iconPath, nodeThreadPool);
|
||||
} else if (scoreRow.getFileDTO() != null) {
|
||||
return new FileNode(key.getSearchResults(), scoreRow.getFileDTO(), true, nodeThreadPool);
|
||||
}
|
||||
} else if (DataArtifactRowDTO.getTypeIdForClass().equals(typeId)) {
|
||||
return new DataArtifactNode((DataArtifactTableSearchResultsDTO) key.getSearchResults(), (DataArtifactRowDTO) key.getRow(), nodeThreadPool);
|
||||
} else if (FileRowDTO.getTypeIdForClass().equals(typeId)) {
|
||||
return new FileNode(key.getSearchResults(), (FileRowDTO) key.getRow(), true, nodeThreadPool);
|
||||
|
@ -37,8 +37,6 @@ import org.sleuthkit.autopsy.mainui.datamodel.FileTypeMimeSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.DeletedContentSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.FileTypeSizeSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewFilter;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.ScoreViewSearchParams;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeItemDTO;
|
||||
import org.sleuthkit.autopsy.mainui.datamodel.events.DAOAggregateEvent;
|
||||
@ -56,7 +54,6 @@ public class ViewsTypeFactory {
|
||||
|
||||
private static final String FILE_TYPES_ICON = "org/sleuthkit/autopsy/images/file_types.png";
|
||||
private static final String SIZE_ICON = "org/sleuthkit/autopsy/images/file-size-16.png";
|
||||
private static final String SCORE_ICON = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
|
||||
|
||||
/**
|
||||
* Node for file extensions parent in the tree.
|
||||
@ -122,22 +119,6 @@ public class ViewsTypeFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent of file size nodes in the tree.
|
||||
*/
|
||||
@Messages({"ViewsTypeFactory_ScoreParentNode_displayName=Score"})
|
||||
private static class ScoreParentNode extends StaticTreeNode {
|
||||
|
||||
ScoreParentNode(Long dataSourceId) {
|
||||
super(
|
||||
"FILE_VIEW_SCORE_PARENT",
|
||||
Bundle.ViewsTypeFactory_ScoreParentNode_displayName(),
|
||||
SCORE_ICON,
|
||||
new ScoreContentFactory(dataSourceId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 'File Types' children in the tree.
|
||||
*/
|
||||
@ -177,8 +158,7 @@ public class ViewsTypeFactory {
|
||||
super(ImmutableList.of(
|
||||
new FileTypesParentNode(dataSourceId),
|
||||
new DeletedParentNode(dataSourceId),
|
||||
new SizeParentNode(dataSourceId),
|
||||
new ScoreParentNode(dataSourceId)
|
||||
new SizeParentNode(dataSourceId)
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -651,108 +631,4 @@ public class ViewsTypeFactory {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory for creating deleted content tree nodes.
|
||||
*/
|
||||
public static class ScoreContentFactory extends TreeChildFactory<ScoreViewSearchParams> {
|
||||
|
||||
private final Long dataSourceId;
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param dataSourceId The data source to filter files to or null.
|
||||
*/
|
||||
public ScoreContentFactory(Long dataSourceId) {
|
||||
this.dataSourceId = dataSourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeNode<ScoreViewSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> rowData) {
|
||||
return new ScoreContentTypeNode(rowData);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO<? extends ScoreViewSearchParams> getChildResults() throws IllegalArgumentException, ExecutionException {
|
||||
return MainDAO.getInstance().getViewsDAO().getScoreContentCounts(dataSourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleDAOAggregateEvent(DAOAggregateEvent aggEvt) {
|
||||
for (DAOEvent evt : aggEvt.getEvents()) {
|
||||
if (evt instanceof TreeEvent) {
|
||||
TreeResultsDTO.TreeItemDTO<DeletedContentSearchParams> treeItem = super.getTypedTreeItem((TreeEvent) evt, DeletedContentSearchParams.class);
|
||||
// if search params has null filter, trigger full refresh
|
||||
if (treeItem != null && treeItem.getSearchParams().getFilter() == null) {
|
||||
super.update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.handleDAOAggregateEvent(aggEvt);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> getOrCreateRelevantChild(TreeEvent treeEvt) {
|
||||
TreeResultsDTO.TreeItemDTO<ScoreViewSearchParams> originalTreeItem = super.getTypedTreeItem(treeEvt, ScoreViewSearchParams.class);
|
||||
|
||||
if (originalTreeItem != null
|
||||
// only create child if size filter is present (if null, update should be triggered separately)
|
||||
&& originalTreeItem.getSearchParams().getFilter() != 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.
|
||||
ScoreViewSearchParams searchParam = originalTreeItem.getSearchParams();
|
||||
return new TreeResultsDTO.TreeItemDTO<>(
|
||||
ScoreViewSearchParams.getTypeId(),
|
||||
new ScoreViewSearchParams(searchParam.getFilter(), this.dataSourceId),
|
||||
searchParam.getFilter(),
|
||||
searchParam.getFilter().getDisplayName(),
|
||||
originalTreeItem.getDisplayCount());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(TreeItemDTO<? extends ScoreViewSearchParams> o1, TreeItemDTO<? extends ScoreViewSearchParams> o2) {
|
||||
return Integer.compare(o1.getSearchParams().getFilter().getId(), o2.getSearchParams().getFilter().getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a deleted content tree node.
|
||||
*/
|
||||
static class ScoreContentTypeNode extends TreeNode<ScoreViewSearchParams> {
|
||||
|
||||
private static final String BAD_SCORE_ICON = SCORE_ICON;
|
||||
private static final String SUS_SCORE_ICON = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
|
||||
|
||||
private static String getIcon(ScoreViewFilter filter) {
|
||||
switch (filter) {
|
||||
case SUSPICIOUS:
|
||||
return SUS_SCORE_ICON;
|
||||
case BAD:
|
||||
default:
|
||||
return BAD_SCORE_ICON;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main constructor.
|
||||
*
|
||||
* @param itemData The data for the node.
|
||||
*/
|
||||
ScoreContentTypeNode(TreeResultsDTO.TreeItemDTO<? extends ScoreViewSearchParams> itemData) {
|
||||
super("SCORE_CONTENT_" + itemData.getSearchParams().getFilter().name(), getIcon(itemData.getSearchParams().getFilter()), itemData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondSelection(DataResultTopComponent dataResultPanel) {
|
||||
dataResultPanel.displayScoreContent(this.getItemData().getSearchParams());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user