diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java index b1e6d83482920b4dd18113814b6a6b31de373ce0..6381acd32d53f9e0a07e9a7c641a4977bc64e47e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/IngestJobInfoPanel.java @@ -31,7 +31,9 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.table.AbstractTableModel; import org.openide.util.NbBundle.Messages; +import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.IngestModuleInfo; @@ -47,6 +49,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel { private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName()); private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); + private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE); + private List<IngestJobInfo> ingestJobs; private final List<IngestJobInfo> ingestJobsForSelectedDataSource = new ArrayList<>(); private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel(); @@ -79,6 +83,16 @@ private void customizeComponents() { refresh(); } }); + + Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> { + if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) { + return; + } + + if (CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName())) { + refresh(); + } + }); } /** @@ -110,9 +124,15 @@ public void setDataSource(DataSource selectedDataSource) { */ private void refresh() { try { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - this.ingestJobs = skCase.getIngestJobs(); - setDataSource(selectedDataSource); + if (Case.isCaseOpen()) { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + this.ingestJobs = skCase.getIngestJobs(); + setDataSource(selectedDataSource); + } else { + this.ingestJobs = new ArrayList<>(); + setDataSource(null); + } + } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED index fe998b0e576f0725efbfdb126f0f099b092b8c27..eb7affe9eee2d095e86f09304e40d64f11d2f116 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED @@ -1,4 +1,6 @@ CTL_DataSourceSummaryAction=Data Source Summary +DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count +DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java index c2d8263056b6efef059ebd006fcfbae0183a8e69..dcbf9bb82d46367bd6a56124c640a723ef24cd27 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java @@ -18,7 +18,8 @@ */ package org.sleuthkit.autopsy.casemodule.datasourcesummary; -import java.util.ArrayList; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -43,6 +44,297 @@ final class DataSourceInfoUtilities { private static final Logger logger = Logger.getLogger(DataSourceInfoUtilities.class.getName()); + /** + * Gets a count of files for a particular datasource where it is not a + * virtual directory and has a name. + * + * @param currentDataSource The datasource. + * @param additionalWhere Additional sql where clauses. + * @param onError The message to log on error. + * + * @return The count of files or null on error. + */ + private static Long getCountOfFiles(DataSource currentDataSource, String additionalWhere, String onError) { + if (currentDataSource != null) { + try { + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + return skCase.countFilesWhere( + "dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() + + " AND name<>''" + + " AND data_source_obj_id=" + currentDataSource.getId() + + " AND " + additionalWhere); + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.WARNING, onError, ex); + //unable to get count of files for the specified types cell will be displayed as empty + } + } + return null; + } + + /** + * Get count of files in a data source. + * + * @param currentDataSource The data source. + * + * @return The count. + */ + static Long getCountOfFiles(DataSource currentDataSource) { + return getCountOfFiles(currentDataSource, + "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(), + "Unable to get count of files, providing empty results"); + } + + /** + * Get count of unallocated files in a data source. + * + * @param currentDataSource The data source. + * + * @return The count. + */ + static Long getCountOfUnallocatedFiles(DataSource currentDataSource) { + return getCountOfFiles(currentDataSource, + "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() + + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(), + "Unable to get counts of unallocated files for datasource, providing empty results"); + } + + /** + * Get count of directories in a data source. + * + * @param currentDataSource The data source. + * + * @return The count. + */ + static Long getCountOfDirectories(DataSource currentDataSource) { + return getCountOfFiles(currentDataSource, + "type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() + + " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue(), + "Unable to get count of directories for datasource, providing empty results"); + } + + /** + * Get count of slack files in a data source. + * + * @param currentDataSource The data source. + * + * @return The count. + */ + static Long getCountOfSlackFiles(DataSource currentDataSource) { + return getCountOfFiles(currentDataSource, + "type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(), + "Unable to get count of slack files for datasources, providing empty results"); + } + + /** + * An interface for handling a result set and returning a value. + */ + private interface ResultSetHandler<T> { + + T process(ResultSet resultset) throws SQLException; + } + + /** + * Retrieves a result based on the provided query. + * + * @param query The query. + * @param processor The result set handler. + * @param errorMessage The error message to display if there is an error + * retrieving the resultset. + * + * @return The ResultSetHandler value or null if no ResultSet could be + * obtained. + */ + private static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) { + try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { + ResultSet resultSet = dbQuery.getResultSet(); + try { + return processor.process(resultSet); + } catch (SQLException ex) { + logger.log(Level.WARNING, errorMessage, ex); + } + } catch (TskCoreException | NoCurrentCaseException ex) { + logger.log(Level.WARNING, errorMessage, ex); + } + return null; + } + + /** + * Gets the size of unallocated files in a particular datasource. + * + * @param currentDataSource The data source. + * + * @return The size or null if the query could not be executed. + */ + static Long getSizeOfUnallocatedFiles(DataSource currentDataSource) { + if (currentDataSource == null) { + return null; + } + + final String valueParam = "value"; + final String countParam = "count"; + String query = "SELECT SUM(size) AS " + valueParam + ", COUNT(*) AS " + countParam + + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() + + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() + + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() + + " AND name<>''" + + " AND data_source_obj_id=" + currentDataSource.getId(); + + ResultSetHandler<Long> handler = (resultSet) -> { + if (resultSet.next()) { + // ensure that there is an unallocated count result that is attached to this data source + long resultCount = resultSet.getLong(valueParam); + return (resultCount > 0) ? resultSet.getLong(valueParam) : null; + } else { + return null; + } + }; + String errorMessage = "Unable to get size of unallocated files; returning null."; + + return getBaseQueryResult(query, handler, errorMessage); + } + + /** + * Retrieves counts for each artifact type in a data source. + * + * @param selectedDataSource The data source. + * + * @return A mapping of artifact type name to the counts or null if there + * was an error executing the query. + */ + static Map<String, Long> getCountsOfArtifactsByType(DataSource selectedDataSource) { + if (selectedDataSource == null) { + return Collections.emptyMap(); + } + + final String nameParam = "name"; + final String valueParam = "value"; + String query + = "SELECT bbt.display_name AS " + nameParam + ", COUNT(*) AS " + valueParam + + " FROM blackboard_artifacts bba " + + " INNER JOIN blackboard_artifact_types bbt ON bba.artifact_type_id = bbt.artifact_type_id" + + " WHERE bba.data_source_obj_id =" + selectedDataSource.getId() + + " GROUP BY bbt.display_name"; + + ResultSetHandler<Map<String, Long>> handler = (resultSet) -> { + Map<String, Long> toRet = new HashMap<>(); + while (resultSet.next()) { + try { + toRet.put(resultSet.getString(nameParam), resultSet.getLong(valueParam)); + } catch (SQLException ex) { + logger.log(Level.WARNING, "Failed to get a result pair from the result set.", ex); + } + } + + return toRet; + }; + + String errorMessage = "Unable to get artifact type counts; returning null."; + + return getBaseQueryResult(query, handler, errorMessage); + } + + /** + * Generates a string which is a concatenation of the value received from + * the result set. + * + * @param query The query. + * @param valueParam The parameter for the value in the result set. + * @param separator The string separator used in concatenation. + * @param errorMessage The error message if the result set could not + * be received. + * @param singleErrorMessage The error message if a single result could not + * be obtained. + * + * @return The concatenated string or null if the query could not be + * executed. + */ + private static String getConcattedStringsResult(String query, String valueParam, String separator, String errorMessage, String singleErrorMessage) { + ResultSetHandler<String> handler = (resultSet) -> { + String toRet = ""; + boolean first = true; + while (resultSet.next()) { + try { + if (first) { + first = false; + } else { + toRet += separator; + } + toRet += resultSet.getString(valueParam); + } catch (SQLException ex) { + logger.log(Level.WARNING, singleErrorMessage, ex); + } + } + + return toRet; + }; + + return getBaseQueryResult(query, handler, errorMessage); + } + + /** + * Generates a concatenated string value based on the text value of a + * particular attribute in a particular artifact for a specific data source. + * + * @param dataSourceId The data source id. + * @param artifactTypeId The artifact type id. + * @param attributeTypeId The attribute type id. + * + * @return The concatenated value or null if the query could not be + * executed. + */ + private static String getConcattedAttrValue(long dataSourceId, int artifactTypeId, int attributeTypeId) { + final String valueParam = "concatted_attribute_value"; + String query = "SELECT attr.value_text AS " + valueParam + + " FROM blackboard_artifacts bba " + + " INNER JOIN blackboard_attributes attr ON bba.artifact_id = attr.artifact_id " + + " WHERE bba.data_source_obj_id = " + dataSourceId + + " AND bba.artifact_type_id = " + artifactTypeId + + " AND attr.attribute_type_id = " + attributeTypeId; + + String errorMessage = "Unable to execute query to retrieve concatted attribute values."; + String singleErrorMessage = "There was an error retrieving one of the results. That result will be omitted from concatted value."; + String separator = ", "; + return getConcattedStringsResult(query, valueParam, separator, errorMessage, singleErrorMessage); + } + + /** + * Retrieves the concatenation of operating system attributes for a + * particular data source. + * + * @param dataSource The data source. + * + * @return The concatenated value or null if the query could not be + * executed. + */ + static String getOperatingSystems(DataSource dataSource) { + if (dataSource == null) { + return null; + } + + return getConcattedAttrValue(dataSource.getId(), + BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID()); + } + + /** + * Retrieves the concatenation of data source usage for a particular data + * source. + * + * @param dataSource The data source. + * + * @return The concatenated value or null if the query could not be + * executed. + */ + static String getDataSourceType(DataSource dataSource) { + if (dataSource == null) { + return null; + } + + return getConcattedAttrValue(dataSource.getId(), + BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE.getTypeID(), + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID()); + } + /** * Get a map containing the TSK_DATA_SOURCE_USAGE description attributes * associated with each data source in the current case. @@ -147,39 +439,6 @@ static Map<Long, Long> getCountsOfTags() { } } - /** - * Get a map containing the names of operating systems joined in a comma - * seperated list to the Data Source they exist on in the current case. No - * item will exist in the map for data sources which do not contain - * TS_OS_INFO artifacts which have a program name. - * - * @return Collection which maps datasource id to a String which is a comma - * seperated list of Operating system names found on the data - * source. - */ - static Map<Long, String> getOperatingSystems() { - Map<Long, String> osDetailMap = new HashMap<>(); - try { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - ArrayList<BlackboardArtifact> osInfoArtifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO); - for (BlackboardArtifact osInfo : osInfoArtifacts) { - BlackboardAttribute programName = osInfo.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME)); - if (programName != null) { - String currentOsString = osDetailMap.get(osInfo.getDataSource().getId()); - if (currentOsString == null || currentOsString.isEmpty()) { - currentOsString = programName.getValueString(); - } else { - currentOsString = currentOsString + ", " + programName.getValueString(); - } - osDetailMap.put(osInfo.getDataSource().getId(), currentOsString); - } - } - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "Failed to load OS info artifacts.", ex); - } - return osDetailMap; - } - /** * Get the number of files in the case database for the current data source * which have the specified mimetypes. @@ -212,134 +471,6 @@ static Long getCountOfFilesForMimeTypes(DataSource currentDataSource, Set<String return null; } - /** - * Get a map containing the number of unallocated files in each data source - * in the current case. - * - * @return Collection which maps datasource id to a count for the number of - * unallocated files in the datasource, will only contain entries - * for datasources which have at least 1 unallocated file - */ - static Map<Long, Long> getCountsOfUnallocatedFiles() { - try { - final String countUnallocatedFilesQuery = "data_source_obj_id, COUNT(*) AS value" - + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() - + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() - + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS - return getValuesMap(countUnallocatedFilesQuery); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get counts of unallocated files for all datasources, providing empty results", ex); - return Collections.emptyMap(); - } - } - - /** - * Get a map containing the total size of unallocated files in each data - * source in the current case. - * - * @return Collection which maps datasource id to a total size in bytes of - * unallocated files in the datasource, will only contain entries - * for datasources which have at least 1 unallocated file - */ - static Map<Long, Long> getSizeOfUnallocatedFiles() { - try { - final String countUnallocatedFilesQuery = "data_source_obj_id, sum(size) AS value" - + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() - + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue() - + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS - return getValuesMap(countUnallocatedFilesQuery); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get size of unallocated files for all datasources, providing empty results", ex); - return Collections.emptyMap(); - } - } - - /** - * Get a map containing the number of directories in each data source in the - * current case. - * - * @return Collection which maps datasource id to a count for the number of - * directories in the datasource, will only contain entries for - * datasources which have at least 1 directory - */ - static Map<Long, Long> getCountsOfDirectories() { - try { - final String countDirectoriesQuery = "data_source_obj_id, COUNT(*) AS value" - + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() - + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue() - + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS - return getValuesMap(countDirectoriesQuery); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get counts of directories for all datasources, providing empty results", ex); - return Collections.emptyMap(); - } - } - - /** - * Get a map containing the number of slack files in each data source in the - * current case. - * - * @return Collection which maps datasource id to a count for the number of - * slack files in the datasource, will only contain entries for - * datasources which have at least 1 slack file - */ - static Map<Long, Long> getCountsOfSlackFiles() { - try { - final String countSlackFilesQuery = "data_source_obj_id, COUNT(*) AS value" - + " FROM tsk_files WHERE type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() - + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS - return getValuesMap(countSlackFilesQuery); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get counts of slack files for all datasources, providing empty results", ex); - return Collections.emptyMap(); - } - } - - /** - * Get a map containing maps which map artifact type to the number of times - * it exosts in each data source in the current case. - * - * @return Collection which maps datasource id to maps of artifact display - * name to number of occurences in the datasource, will only contain - * entries for artifacts which have at least one occurence in the - * data source. - */ - static Map<Long, Map<String, Long>> getCountsOfArtifactsByType() { - try { - final String countArtifactsQuery = "blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name AS label, COUNT(*) AS value" - + " FROM blackboard_artifacts, blackboard_artifact_types" - + " WHERE blackboard_artifacts.artifact_type_id = blackboard_artifact_types.artifact_type_id" - + " GROUP BY blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name"; - return getLabeledValuesMap(countArtifactsQuery); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get counts of all artifact types for all datasources, providing empty results", ex); - return Collections.emptyMap(); - } - } - - /** - * Helper method to execute a select query with a - * DataSourceLabeledValueCallback. - * - * @param query the portion of the query which should follow the SELECT - * - * @return a map of datasource object IDs to maps of String labels to the - * values associated with them. - * - * @throws TskCoreException - * @throws NoCurrentCaseException - */ - private static Map<Long, Map<String, Long>> getLabeledValuesMap(String query) throws TskCoreException, NoCurrentCaseException { - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - DataSourceLabeledValueCallback callback = new DataSourceLabeledValueCallback(); - skCase.getCaseDbAccessManager().select(query, callback); - return callback.getMapOfLabeledValues(); - } - /** * Helper method to execute a select query with a * DataSourceSingleValueCallback. diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form index d3a17e0205ecda22e44a44edd7d0dd442269844b..9a82746da613c0df11acb31ecee348228fe5144f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form @@ -70,18 +70,13 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <SubComponents> <Component class="javax.swing.JTable" name="fileCountsByMimeTypeTable"> - <Properties> - <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> - <Connection code="filesByMimeTypeTableModel" type="code"/> - </Property> - </Properties> </Component> </SubComponents> </Container> <Component class="javax.swing.JLabel" name="byMimeTypeLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> </Component> @@ -93,25 +88,20 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <SubComponents> <Component class="javax.swing.JTable" name="fileCountsByCategoryTable"> - <Properties> - <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor"> - <Connection code="filesByCategoryTableModel" type="code"/> - </Property> - </Properties> </Component> </SubComponents> </Container> <Component class="javax.swing.JLabel" name="byCategoryLabel"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> </Component> <Component class="javax.swing.JLabel" name="jLabel1"> <Properties> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> </Component> @@ -123,15 +113,6 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <SubComponents> <Component class="javax.swing.JTable" name="artifactCountsTable"> - <Properties> - <Property name="autoCreateRowSorter" type="boolean" value="true"/> - <Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor"> - <Table columnCount="2" rowCount="0"> - <Column editable="false" title="Result Type" type="java.lang.Object"/> - <Column editable="false" title="Count" type="java.lang.Object"/> - </Table> - </Property> - </Properties> </Component> </SubComponents> </Container> diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java index 1d99f1c44520756d1efbaf41540299964387eb40..6bb6603c11520b86f4b6a0986d0e9a47c4863d58 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java @@ -18,15 +18,12 @@ */ package org.sleuthkit.autopsy.casemodule.datasourcesummary; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JLabel; -import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.DefaultTableModel; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.FileTypeUtils; import org.sleuthkit.datamodel.DataSource; @@ -34,69 +31,224 @@ * Panel for displaying summary information on the known files present in the * specified DataSource */ +@Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count", + "DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type", + "DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count" +}) class DataSourceSummaryCountsPanel extends javax.swing.JPanel { + // Result returned for a data model if no data found. + private static final Object[][] EMPTY_PAIRS = new Object[][]{}; + + // column headers for mime type table + private static final Object[] MIME_TYPE_COLUMN_HEADERS = new Object[]{ + Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header(), + Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header() + }; + + // column headers for file by category table + private static final Object[] FILE_BY_CATEGORY_COLUMN_HEADERS = new Object[]{ + Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header(), + Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header() + }; + + // column headers for artifact counts table + private static final Object[] ARTIFACT_COUNTS_COLUMN_HEADERS = new Object[]{ + Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_type_header(), + Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_count_header() + }; + private static final long serialVersionUID = 1L; - private FilesByMimeTypeTableModel filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(null); - private FilesByCategoryTableModel filesByCategoryTableModel = new FilesByCategoryTableModel(null); private static final Logger logger = Logger.getLogger(DataSourceSummaryCountsPanel.class.getName()); - private final Map<Long, Long> allFilesCountsMap; - private final Map<Long, Long> slackFilesCountsMap; - private final Map<Long, Long> directoriesCountsMap; - private final Map<Long, Long> unallocatedFilesCountsMap; - private final Map<Long, Map<String, Long>> artifactsByTypeCountsMap; private final DefaultTableCellRenderer rightAlignedRenderer = new DefaultTableCellRenderer(); + private DataSource dataSource; + /** * Creates new form DataSourceSummaryCountsPanel */ - DataSourceSummaryCountsPanel(Map<Long, Long> fileCountsMap) { - this.allFilesCountsMap = fileCountsMap; - this.slackFilesCountsMap = DataSourceInfoUtilities.getCountsOfSlackFiles(); - this.directoriesCountsMap = DataSourceInfoUtilities.getCountsOfDirectories(); - this.unallocatedFilesCountsMap = DataSourceInfoUtilities.getCountsOfUnallocatedFiles(); - this.artifactsByTypeCountsMap = DataSourceInfoUtilities.getCountsOfArtifactsByType(); + DataSourceSummaryCountsPanel() { rightAlignedRenderer.setHorizontalAlignment(JLabel.RIGHT); initComponents(); fileCountsByMimeTypeTable.getTableHeader().setReorderingAllowed(false); fileCountsByCategoryTable.getTableHeader().setReorderingAllowed(false); + artifactCountsTable.getTableHeader().setReorderingAllowed(false); + setDataSource(null); } /** - * Specify the DataSource to display file information for + * The datasource currently used as the model in this panel. * - * @param selectedDataSource the DataSource to display file information for + * @return The datasource currently being used as the model in this panel. */ - void updateCountsTableData(DataSource selectedDataSource) { - filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(selectedDataSource); - fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel); + public DataSource getDataSource() { + return dataSource; + } + + /** + * Sets datasource to visualize in the panel. + * + * @param dataSource The datasource to use in this panel. + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + if (dataSource == null || !Case.isCaseOpen()) { + updateCountsTableData(EMPTY_PAIRS, + EMPTY_PAIRS, + EMPTY_PAIRS); + } else { + updateCountsTableData(getMimeTypeModel(dataSource), + getFileCategoryModel(dataSource), + getArtifactCountsModel(dataSource)); + } + + } + + /** + * Specify the DataSource to display file information for. + * + * @param mimeTypeDataModel The mime type data model. + * @param fileCategoryDataModel The file category data model. + * @param artifactDataModel The artifact type data model. + */ + private void updateCountsTableData(Object[][] mimeTypeDataModel, Object[][] fileCategoryDataModel, Object[][] artifactDataModel) { + fileCountsByMimeTypeTable.setModel(new NonEditableTableModel(mimeTypeDataModel, MIME_TYPE_COLUMN_HEADERS)); fileCountsByMimeTypeTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); fileCountsByMimeTypeTable.getColumnModel().getColumn(0).setPreferredWidth(130); - filesByCategoryTableModel = new FilesByCategoryTableModel(selectedDataSource); - fileCountsByCategoryTable.setModel(filesByCategoryTableModel); + + fileCountsByCategoryTable.setModel(new NonEditableTableModel(fileCategoryDataModel, FILE_BY_CATEGORY_COLUMN_HEADERS)); fileCountsByCategoryTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); fileCountsByCategoryTable.getColumnModel().getColumn(0).setPreferredWidth(130); - updateArtifactCounts(selectedDataSource); + + artifactCountsTable.setModel(new NonEditableTableModel(artifactDataModel, ARTIFACT_COUNTS_COLUMN_HEADERS)); + artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230); + artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); + this.repaint(); } /** - * Helper method to update the artifact specific counts by clearing the - * table and adding counts for the artifacts which exist in the selected - * data source. + * Determines the JTable data model for datasource mime types. + * + * @param dataSource The DataSource. * - * @param selectedDataSource the data source to display artifact counts for + * @return The model to be used with a JTable. */ - private void updateArtifactCounts(DataSource selectedDataSource) { - ((DefaultTableModel) artifactCountsTable.getModel()).setRowCount(0); - if (selectedDataSource != null && artifactsByTypeCountsMap.get(selectedDataSource.getId()) != null) { - Map<String, Long> artifactCounts = artifactsByTypeCountsMap.get(selectedDataSource.getId()); - for (String key : artifactCounts.keySet()) { - ((DefaultTableModel) artifactCountsTable.getModel()).addRow(new Object[]{key, artifactCounts.get(key)}); - } + @Messages({ + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables" + }) + private static Object[][] getMimeTypeModel(DataSource dataSource) { + return new Object[][]{ + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row(), + getCount(dataSource, FileTypeUtils.FileTypeCategory.IMAGE)}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row(), + getCount(dataSource, FileTypeUtils.FileTypeCategory.VIDEO)}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row(), + getCount(dataSource, FileTypeUtils.FileTypeCategory.AUDIO)}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row(), + getCount(dataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS)}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row(), + getCount(dataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE)} + }; + } + + /** + * Retrieves the counts of files of a particular mime type for a particular + * DataSource. + * + * @param dataSource The DataSource. + * @param category The mime type category. + * + * @return The count. + */ + private static Long getCount(DataSource dataSource, FileTypeUtils.FileTypeCategory category) { + return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(dataSource, category.getMediaTypes()); + } + + /** + * Determines the JTable data model for datasource file categories. + * + * @param dataSource The DataSource. + * + * @return The model to be used with a JTable. + */ + @Messages({ + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack", + "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory" + }) + private static Object[][] getFileCategoryModel(DataSource selectedDataSource) { + Long fileCount = zeroIfNull(DataSourceInfoUtilities.getCountOfFiles(selectedDataSource)); + Long unallocatedFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfUnallocatedFiles(selectedDataSource)); + Long allocatedFiles = zeroIfNull(getAllocatedCount(fileCount, unallocatedFiles)); + Long slackFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfSlackFiles(selectedDataSource)); + Long directories = zeroIfNull(DataSourceInfoUtilities.getCountOfDirectories(selectedDataSource)); + + return new Object[][]{ + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row(), fileCount}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row(), allocatedFiles}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row(), unallocatedFiles}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row(), slackFiles}, + new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row(), directories} + }; + } + + /** + * Returns 0 if value is null. + * + * @param origValue The original value. + * + * @return The value or 0 if null. + */ + private static Long zeroIfNull(Long origValue) { + return origValue == null ? 0 : origValue; + } + + /** + * Safely gets the allocated files count. + * + * @param allFilesCount The count of all files. + * @param unallocatedFilesCount The count of unallocated files. + * + * @return The count of allocated files. + */ + private static long getAllocatedCount(Long allFilesCount, Long unallocatedFilesCount) { + if (allFilesCount == null) { + return 0; + } else if (unallocatedFilesCount == null) { + return allFilesCount; + } else { + return allFilesCount - unallocatedFilesCount; } - artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230); - artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); + } + + /** + * The counts of different artifact types found in a DataSource. + * + * @param selectedDataSource The DataSource. + * + * @return The JTable data model of counts of artifact types. + */ + private static Object[][] getArtifactCountsModel(DataSource selectedDataSource) { + Map<String, Long> artifactMapping = DataSourceInfoUtilities.getCountsOfArtifactsByType(selectedDataSource); + if (artifactMapping == null) { + return EMPTY_PAIRS; + } + + return artifactMapping.entrySet().stream() + .filter((entrySet) -> entrySet != null && entrySet.getKey() != null) + .sorted((a, b) -> a.getKey().compareTo(b.getKey())) + .map((entrySet) -> new Object[]{entrySet.getKey(), entrySet.getValue()}) + .toArray(Object[][]::new); } /** @@ -118,35 +270,16 @@ private void initComponents() { artifactCountsScrollPane = new javax.swing.JScrollPane(); artifactCountsTable = new javax.swing.JTable(); - fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel); fileCountsByMimeTypeScrollPane.setViewportView(fileCountsByMimeTypeTable); org.openide.awt.Mnemonics.setLocalizedText(byMimeTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byMimeTypeLabel.text")); // NOI18N - fileCountsByCategoryTable.setModel(filesByCategoryTableModel); fileCountsByCategoryScrollPane.setViewportView(fileCountsByCategoryTable); org.openide.awt.Mnemonics.setLocalizedText(byCategoryLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byCategoryLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.jLabel1.text")); // NOI18N - artifactCountsTable.setAutoCreateRowSorter(true); - artifactCountsTable.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - "Result Type", "Count" - } - ) { - boolean[] canEdit = new boolean [] { - false, false - }; - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return canEdit [columnIndex]; - } - }); artifactCountsScrollPane.setViewportView(artifactCountsTable); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -206,190 +339,4 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { private javax.swing.JTable fileCountsByMimeTypeTable; private javax.swing.JLabel jLabel1; // End of variables declaration//GEN-END:variables - - /** - * Table model for the files table model to display counts of specific file - * types by mime type found in the currently selected data source. - */ - @Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type", - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count"}) - private class FilesByMimeTypeTableModel extends AbstractTableModel { - - private static final long serialVersionUID = 1L; - private final DataSource currentDataSource; - private final List<String> columnHeaders = new ArrayList<>(); - private static final int IMAGES_ROW_INDEX = 0; - private static final int VIDEOS_ROW_INDEX = 1; - private static final int AUDIO_ROW_INDEX = 2; - private static final int DOCUMENTS_ROW_INDEX = 3; - private static final int EXECUTABLES_ROW_INDEX = 4; - - /** - * Create a FilesByMimeTypeTableModel for the speicified datasource. - * - * @param selectedDataSource the datasource which this - * FilesByMimeTypeTablemodel will represent - */ - FilesByMimeTypeTableModel(DataSource selectedDataSource) { - columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header()); - columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header()); - currentDataSource = selectedDataSource; - } - - @Override - public int getRowCount() { - //should be kept equal to the number of types we are displaying in the tables - return 5; - } - - @Override - public int getColumnCount() { - return columnHeaders.size(); - } - - @Messages({ - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images", - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos", - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio", - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents", - "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables" - }) - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if (columnIndex == 0) { - switch (rowIndex) { - case IMAGES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row(); - case VIDEOS_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row(); - case AUDIO_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row(); - case DOCUMENTS_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row(); - case EXECUTABLES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row(); - default: - break; - } - } else if (columnIndex == 1) { - switch (rowIndex) { - case 0: - return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes()); - case 1: - return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes()); - case 2: - return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes()); - case 3: - return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS.getMediaTypes()); - case 4: - return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes()); - default: - break; - } - } - return null; - } - - @Override - public String getColumnName(int column) { - return columnHeaders.get(column); - } - } - - /** - * Table model for the files table model to display counts of specific file - * types by category found in the currently selected data source. - */ - @Messages({"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type", - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count"}) - private class FilesByCategoryTableModel extends AbstractTableModel { - - private static final long serialVersionUID = 1L; - private final DataSource currentDataSource; - private final List<String> columnHeaders = new ArrayList<>(); - private static final int ALL_FILES_ROW_INDEX = 0; - private static final int ALLOCATED_FILES_ROW_INDEX = 1; - private static final int UNALLOCATED_FILES_ROW_INDEX = 2; - private static final int SLACK_FILES_ROW_INDEX = 3; - private static final int DIRECTORIES_ROW_INDEX = 4; - /** - * Create a FilesByCategoryTableModel for the speicified datasource. - * - * @param selectedDataSource the datasource which this - * FilesByCategoryTablemodel will represent - */ - FilesByCategoryTableModel(DataSource selectedDataSource) { - columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header()); - columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header()); - currentDataSource = selectedDataSource; - } - - @Override - public int getRowCount() { - //should be kept equal to the number of types we are displaying in the tables - return 5; - } - - @Override - public int getColumnCount() { - return columnHeaders.size(); - } - - @Messages({ - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All", - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated", - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated", - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack", - "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory" - }) - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if (columnIndex == 0) { - switch (rowIndex) { - case ALL_FILES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row(); - case ALLOCATED_FILES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row(); - case UNALLOCATED_FILES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row(); - case SLACK_FILES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row(); - case DIRECTORIES_ROW_INDEX: - return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row(); - default: - break; - } - } else if (columnIndex == 1 && currentDataSource != null) { - switch (rowIndex) { - case 0: - return allFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : allFilesCountsMap.get(currentDataSource.getId()); - case 1: - //All files should be either allocated or unallocated as dir_flags only has two values so any file that isn't unallocated is allocated - Long unallocatedFilesCount = unallocatedFilesCountsMap.get(currentDataSource.getId()); - Long allFilesCount = allFilesCountsMap.get(currentDataSource.getId()); - if (allFilesCount == null) { - return 0; - } else if (unallocatedFilesCount == null) { - return allFilesCount; - } else { - return allFilesCount - unallocatedFilesCount; - } - case 2: - return unallocatedFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : unallocatedFilesCountsMap.get(currentDataSource.getId()); - case 3: - return slackFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : slackFilesCountsMap.get(currentDataSource.getId()); - case 4: - return directoriesCountsMap.get(currentDataSource.getId()) == null ? 0 : directoriesCountsMap.get(currentDataSource.getId()); - default: - break; - } - } - return null; - } - - @Override - public String getColumnName(int column) { - return columnHeaders.get(column); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java index 1b898f49951a5a6d26cbe273f228aaa7ec537015..21caa2c4b85414f80ddf1a9c33d7426dab4082b1 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDetailsPanel.java @@ -19,12 +19,12 @@ package org.sleuthkit.autopsy.casemodule.datasourcesummary; import java.text.DecimalFormat; -import java.util.Map; -import java.util.HashMap; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.table.DefaultTableModel; +import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.TskCoreException; @@ -36,23 +36,47 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel { //Because this panel was made using the gridbaglayout and netbean's Customize Layout tool it will be best to continue to modify it through that private static final long serialVersionUID = 1L; - private Map<Long, String> osDetailMap = new HashMap<>(); private static final Integer SIZE_COVERSION_CONSTANT = 1000; private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##"); - private final Map<Long, Long> unallocatedFilesSizeMap; - private final Map<Long, String> usageMap; private static final Logger logger = Logger.getLogger(DataSourceSummaryDetailsPanel.class.getName()); + private DataSource dataSource; + /** * Creates new form DataSourceSummaryDetailsPanel */ @Messages({"DataSourceSummaryDetailsPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.", "DataSourceSummaryDetailsPanel.getDataSources.error.title=Load Failure"}) - DataSourceSummaryDetailsPanel(Map<Long, String> usageMap) { + DataSourceSummaryDetailsPanel() { initComponents(); - this.usageMap = usageMap; - this.unallocatedFilesSizeMap = DataSourceInfoUtilities.getSizeOfUnallocatedFiles(); - osDetailMap = DataSourceInfoUtilities.getOperatingSystems(); + setDataSource(null); + } + + /** + * The datasource currently used as the model in this panel. + * + * @return The datasource currently being used as the model in this panel. + */ + public DataSource getDataSource() { + return dataSource; + } + + /** + * Sets datasource to visualize in the panel. + * + * @param dataSource The datasource to use in this panel. + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + + if (dataSource == null || !Case.isCaseOpen()) { + updateDetailsPanelData(null, null, null, null); + } else { + updateDetailsPanelData(dataSource, + DataSourceInfoUtilities.getSizeOfUnallocatedFiles(dataSource), + DataSourceInfoUtilities.getOperatingSystems(dataSource), + DataSourceInfoUtilities.getDataSourceType(dataSource)); + } } /** @@ -60,75 +84,78 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel { * * @param selectedDataSource the DataSource to display details about. */ - void updateDetailsPanelData(DataSource selectedDataSource) { + private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize, String osDetails, String usage) { clearTableValues(); if (selectedDataSource != null) { - String sizeString = ""; - String sectorSizeString = ""; - String md5String = ""; - String sha1String = ""; - String sha256String = ""; - String acquisitionDetailsString = ""; - String imageTypeString = ""; - String[] filePaths = new String[0]; - String osDetailString = osDetailMap.get(selectedDataSource.getId()) == null ? "" : osDetailMap.get(selectedDataSource.getId()); - String dataSourceTypeString = usageMap.get(selectedDataSource.getId()) == null ? "" : usageMap.get(selectedDataSource.getId()); + unallocatedSizeValue.setText(getSizeString(unallocatedFilesSize)); + operatingSystemValue.setText(StringUtils.isBlank(osDetails) ? "" : osDetails); + dataSourceUsageValue.setText(StringUtils.isBlank(usage) ? "" : usage); + + timeZoneValue.setText(selectedDataSource.getTimeZone()); + displayNameValue.setText(selectedDataSource.getName()); + originalNameValue.setText(selectedDataSource.getName()); + deviceIdValue.setText(selectedDataSource.getDeviceId()); + try { - acquisitionDetailsString = selectedDataSource.getAcquisitionDetails(); + acquisitionDetailsTextArea.setText(selectedDataSource.getAcquisitionDetails()); } catch (TskCoreException ex) { logger.log(Level.WARNING, "Unable to get aquisition details for selected data source", ex); } + if (selectedDataSource instanceof Image) { - imageTypeString = ((Image) selectedDataSource).getType().getName(); - filePaths = ((Image) selectedDataSource).getPaths(); - sizeString = getSizeString(selectedDataSource.getSize()); - sectorSizeString = getSizeString(((Image) selectedDataSource).getSsize()); - try { - //older databases may have null as the hash values - md5String = ((Image) selectedDataSource).getMd5(); - if (md5String == null) { - md5String = ""; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex); - } - try { - sha1String = ((Image) selectedDataSource).getSha1(); - if (sha1String == null) { - sha1String = ""; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex); - } - try { - sha256String = ((Image) selectedDataSource).getSha256(); - if (sha256String == null) { - sha256String = ""; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex); - } + setFieldsForImage((Image) selectedDataSource); + } + } + updateFieldVisibility(); + this.repaint(); + } + + /** + * Sets text fields for an image. This should be called after + * clearTableValues and before updateFieldVisibility to ensure the proper + * rendering. + * + * @param selectedImage The selected image. + */ + private void setFieldsForImage(Image selectedImage) { + imageTypeValue.setText(selectedImage.getType().getName()); + sizeValue.setText(getSizeString(selectedImage.getSize())); + sectorSizeValue.setText(getSizeString(selectedImage.getSsize())); + + for (String path : selectedImage.getPaths()) { + ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path}); + } + + try { + //older databases may have null as the hash values + String md5String = selectedImage.getMd5(); + if (md5String == null) { + md5String = ""; } - displayNameValue.setText(selectedDataSource.getName()); - originalNameValue.setText(selectedDataSource.getName()); - deviceIdValue.setText(selectedDataSource.getDeviceId()); - dataSourceUsageValue.setText(dataSourceTypeString); - operatingSystemValue.setText(osDetailString); - timeZoneValue.setText(selectedDataSource.getTimeZone()); - acquisitionDetailsTextArea.setText(acquisitionDetailsString); - imageTypeValue.setText(imageTypeString); - sizeValue.setText(sizeString); - unallocatedSizeValue.setText(getSizeString(unallocatedFilesSizeMap.get(selectedDataSource.getId()))); - sectorSizeValue.setText(sectorSizeString); md5HashValue.setText(md5String); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex); + } + + try { + String sha1String = selectedImage.getSha1(); + if (sha1String == null) { + sha1String = ""; + } sha1HashValue.setText(sha1String); - sha256HashValue.setText(sha256String); - for (String path : filePaths) { - ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path}); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex); + } + + try { + String sha256String = selectedImage.getSha256(); + if (sha256String == null) { + sha256String = ""; } + sha256HashValue.setText(sha256String); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex); } - updateFieldVisibility(); - this.repaint(); } /** @@ -147,7 +174,7 @@ void updateDetailsPanelData(DataSource selectedDataSource) { "DataSourceSummaryDetailsPanel.units.terabytes= TB", "DataSourceSummaryDetailsPanel.units.petabytes= PB" }) - private String getSizeString(Long size) { + private static String getSizeString(Long size) { if (size == null) { return ""; } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form index 72c45cb5a7fa8551fe403c2c580f71e040519721..272fc9b041d2f743485f269ab50f9f3c877b4981 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form @@ -68,6 +68,11 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <SubComponents> <Container class="javax.swing.JTabbedPane" name="dataSourceTabbedPane"> + <AuxValues> + <AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="dataSourceSummaryTabbedPane"/> + <AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/> + <AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/> + </AuxValues> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> <JSplitPaneConstraints position="right"/> diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 3c68b7b3edf348dd3497d50ab56c5103d191d729..e1197fa83a809d9fa249ab47ad1d3e7b1a67203d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -27,7 +27,6 @@ import java.util.Set; import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle.Messages; -import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent; import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason; @@ -41,10 +40,8 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser private static final long serialVersionUID = 1L; private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED); - private final DataSourceSummaryCountsPanel countsPanel; - private final DataSourceSummaryDetailsPanel detailsPanel; private final DataSourceBrowser dataSourcesPanel; - private final IngestJobInfoPanel ingestHistoryPanel; + private final DataSourceSummaryTabbedPane dataSourceSummaryTabbedPane; /** * Creates new form DataSourceSummaryDialog for displaying a summary of the @@ -61,21 +58,14 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser super(owner, Bundle.DataSourceSummaryDialog_window_title(), true); Map<Long, String> usageMap = DataSourceInfoUtilities.getDataSourceTypes(); Map<Long, Long> fileCountsMap = DataSourceInfoUtilities.getCountsOfFiles(); - countsPanel = new DataSourceSummaryCountsPanel(fileCountsMap); - detailsPanel = new DataSourceSummaryDetailsPanel(usageMap); dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap); - ingestHistoryPanel = new IngestJobInfoPanel(); + dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane(); initComponents(); dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel); - dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel); - dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel); - dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel); dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> { if (!e.getValueIsAdjusting()) { DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource(); - countsPanel.updateCountsTableData(selectedDataSource); - detailsPanel.updateDetailsPanelData(selectedDataSource); - ingestHistoryPanel.setDataSource(selectedDataSource); + dataSourceSummaryTabbedPane.setDataSource(selectedDataSource); this.repaint(); } }); @@ -116,7 +106,7 @@ private void initComponents() { closeButton = new javax.swing.JButton(); dataSourceSummarySplitPane = new javax.swing.JSplitPane(); - dataSourceTabbedPane = new javax.swing.JTabbedPane(); + javax.swing.JTabbedPane dataSourceTabbedPane = dataSourceSummaryTabbedPane; org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryDialog.class, "DataSourceSummaryDialog.closeButton.text")); // NOI18N closeButton.addActionListener(new java.awt.event.ActionListener() { @@ -173,6 +163,5 @@ void selectDataSource(Long dataSourceId) { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton closeButton; private javax.swing.JSplitPane dataSourceSummarySplitPane; - private javax.swing.JTabbedPane dataSourceTabbedPane; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java new file mode 100644 index 0000000000000000000000000000000000000000..e1d15a6b0dfa4385129a4ccebfe5b8ba64fc71f3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryTabbedPane.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.casemodule.datasourcesummary; + +import javax.swing.JTabbedPane; +import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel; +import org.sleuthkit.datamodel.DataSource; + +/** + * A tabbed pane showing the summary of a data source including tabs of: + * DataSourceSummaryCountsPanel, DataSourceSummaryDetailsPanel, and + * IngestJobInfoPanel. + */ +public class DataSourceSummaryTabbedPane extends JTabbedPane { + + private static final long serialVersionUID = 1L; + + private final DataSourceSummaryCountsPanel countsPanel; + private final DataSourceSummaryDetailsPanel detailsPanel; + private final IngestJobInfoPanel ingestHistoryPanel; + + private DataSource dataSource = null; + + /** + * Constructs a tabbed pane showing the summary of a data source. + */ + public DataSourceSummaryTabbedPane() { + countsPanel = new DataSourceSummaryCountsPanel(); + detailsPanel = new DataSourceSummaryDetailsPanel(); + ingestHistoryPanel = new IngestJobInfoPanel(); + + addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel); + addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel); + addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel); + } + + /** + * The datasource currently used as the model in this panel. + * + * @return The datasource currently being used as the model in this panel. + */ + public DataSource getDataSource() { + return dataSource; + } + + /** + * Sets datasource to visualize in the tabbed panel. + * + * @param dataSource The datasource to use in this panel. + */ + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + + detailsPanel.setDataSource(dataSource); + countsPanel.setDataSource(dataSource); + ingestHistoryPanel.setDataSource(dataSource); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8ed965e2abcb841072db0ca891f094037ff7614c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/NonEditableTableModel.java @@ -0,0 +1,37 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.casemodule.datasourcesummary; + +import javax.swing.table.DefaultTableModel; + +/** + * A Table model where cells are not editable. + */ +class NonEditableTableModel extends DefaultTableModel { + private static final long serialVersionUID = 1L; + + NonEditableTableModel(Object[][] data, Object[] columnNames) { + super(data, columnNames); + } + + @Override + public boolean isCellEditable(int row, int column) { + return false; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED new file mode 100644 index 0000000000000000000000000000000000000000..8a629e17a9fef2e1a8d04e445af887cbf78ac329 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/Bundle.properties-MERGED @@ -0,0 +1 @@ +DataSourceSummaryResultViewer_title=Summary diff --git a/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java new file mode 100644 index 0000000000000000000000000000000000000000..1b1b3cb127db37b613f5a7c331e10d90f9c2513f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/resultviewers/summary/DataSourceSummaryResultViewer.java @@ -0,0 +1,137 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.resultviewers.summary; + +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryTabbedPane; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.AbstractDataResultViewer; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; + +/** + * A tabular result viewer that displays a summary of the selected Data Source. + */ +@ServiceProvider(service = DataResultViewer.class) +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DataSourceSummaryResultViewer extends AbstractDataResultViewer { + + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(DataSourceSummaryResultViewer.class.getName()); + + private final String title; + + /** + * Constructs a tabular result viewer that displays a summary of the + * selected Data Source. + */ + public DataSourceSummaryResultViewer() { + this(null); + } + + /** + * Constructs a tabular result viewer that displays a summary of the + * selected Data Source. + * + * @param explorerManager The explorer manager of the ancestor top + * component. + * + */ + @Messages({ + "DataSourceSummaryResultViewer_title=Summary" + }) + public DataSourceSummaryResultViewer(ExplorerManager explorerManager) { + this(explorerManager, Bundle.DataSourceSummaryResultViewer_title()); + } + + /** + * Constructs a tabular result viewer that displays a summary of the + * selected Data Source. + * + * @param explorerManager The explorer manager of the ancestor top + * component. + * @param title The title. + * + */ + public DataSourceSummaryResultViewer(ExplorerManager explorerManager, String title) { + super(explorerManager); + this.title = title; + initComponents(); + } + + @Override + public DataResultViewer createInstance() { + return new DataSourceSummaryResultViewer(); + } + + @Override + public boolean isSupported(Node node) { + return getDataSource(node) != null; + } + + /** + * Returns the datasource attached to the node or null if none can be found. + * + * @param node The node to search. + * + * @return The datasource or null if not found. + */ + private DataSource getDataSource(Node node) { + return node == null ? null : node.getLookup().lookup(DataSource.class); + } + + @Override + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public void setNode(Node node) { + if (!SwingUtilities.isEventDispatchThread()) { + LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread."); + return; + } + + DataSource dataSource = getDataSource(node); + + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + summaryPanel.setDataSource(dataSource); + } finally { + this.setCursor(null); + } + } + + @Override + public String getTitle() { + return title; + } + + private void initComponents() { + summaryPanel = new DataSourceSummaryTabbedPane(); + setLayout(new BorderLayout()); + add(summaryPanel, BorderLayout.CENTER); + } + + private DataSourceSummaryTabbedPane summaryPanel; +}