diff --git a/Core/ivy.xml b/Core/ivy.xml index 63fdd9ed92..4c4bef4f9a 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -50,6 +50,9 @@ + + + diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 61e6a86b04..f82ab0c7af 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -479,6 +479,14 @@ ext/jxmapviewer2-2.4.jar release/modules/ext/jxmapviewer2-2.4.jar + + ext/jfreechart-1.0.19.jar + release/modules/ext/jfreechart-1.0.19.jar + + + ext/jcommon-1.0.23.jar + release/modules/ext/jcommon-1.0.23.jar + ext/jdom-2.0.5-contrib.jar release/modules/ext/jdom-2.0.5-contrib.jar diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties index 24a312f733..462d910dde 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties @@ -33,7 +33,6 @@ DataSourceSummaryDetailsPanel.acquisitionDetailsTextArea.text= DataSourceSummaryDetailsPanel.acquisitionDetailsLabel.text=Acquisition Details: DataSourceSummaryDetailsPanel.unallocatedSizeLabel.text=Unallocated Space: DataSourceSummaryDetailsPanel.unallocatedSizeValue.text= -DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category -DataSourceSummaryCountsPanel.jLabel1.text=Results by Type +DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run 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 ba0d3510c7..58c1fc4516 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties-MERGED @@ -1,6 +1,7 @@ CTL_DataSourceSummaryAction=Data Source Summary DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type +DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count @@ -58,9 +59,11 @@ DataSourceSummaryDetailsPanel.acquisitionDetailsTextArea.text= DataSourceSummaryDetailsPanel.acquisitionDetailsLabel.text=Acquisition Details: DataSourceSummaryDetailsPanel.unallocatedSizeLabel.text=Unallocated Space: DataSourceSummaryDetailsPanel.unallocatedSizeValue.text= -DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category -DataSourceSummaryCountsPanel.jLabel1.text=Results by Type +DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type +DataSourceSummaryDialog.countsTab.title=Counts +DataSourceSummaryDialog.detailsTab.title=Details +DataSourceSummaryDialog.ingestHistoryTab.title=Ingest History DataSourceSummaryDialog.window.title=Data Sources Summary DataSourceSummaryNode.column.dataSourceName.header=Data Source Name DataSourceSummaryNode.column.files.header=Files diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java index 7e66e56682..12f4ad738d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceInfoUtilities.java @@ -45,6 +45,9 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; +import org.sleuthkit.datamodel.TskData.TSK_FS_META_FLAG_ENUM; +import org.sleuthkit.datamodel.TskData.TSK_FS_META_TYPE_ENUM; /** * Utilities for getting information about a data source or all data sources @@ -55,8 +58,8 @@ 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. + * Gets a count of tsk_files for a particular datasource where dir_type is + * not a virtual directory and has a name. * * @param currentDataSource The datasource. * @param additionalWhere Additional sql where clauses. @@ -64,15 +67,15 @@ final class DataSourceInfoUtilities { * * @return The count of files or null on error. */ - private static Long getCountOfFiles(DataSource currentDataSource, String additionalWhere, String onError) { + private static Long getCountOfTskFiles(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() + "data_source_obj_id=" + currentDataSource.getId() + + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() + " AND name<>''" - + " AND data_source_obj_id=" + currentDataSource.getId() - + " AND " + additionalWhere); + + (StringUtils.isBlank(additionalWhere) ? "" : (" 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 @@ -82,18 +85,51 @@ final class DataSourceInfoUtilities { } /** - * Get count of files in a data source. + * Gets a count of regular files for a particular datasource where the + * dir_type and type are 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 getCountOfRegularFiles(DataSource currentDataSource, String additionalWhere, String onError) { + String whereClause = "meta_type=" + TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + + " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(); + + if (StringUtils.isNotBlank(additionalWhere)) { + whereClause += " AND " + additionalWhere; + } + + return getCountOfTskFiles(currentDataSource, whereClause, onError); + } + + /** + * Get count of regular files (not directories) 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(), + return getCountOfRegularFiles(currentDataSource, null, "Unable to get count of files, providing empty results"); } + /** + * Get count of allocated files in a data source. + * + * @param currentDataSource The data source. + * + * @return The count. + */ + static Long getCountOfAllocatedFiles(DataSource currentDataSource) { + return getCountOfRegularFiles(currentDataSource, + getMetaFlagsContainsStatement(TSK_FS_META_FLAG_ENUM.ALLOC), + "Unable to get counts of unallocated files for datasource, providing empty results"); + } + /** * Get count of unallocated files in a data source. * @@ -102,9 +138,9 @@ final class DataSourceInfoUtilities { * @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(), + return getCountOfRegularFiles(currentDataSource, + getMetaFlagsContainsStatement(TSK_FS_META_FLAG_ENUM.UNALLOC) + + " AND type<>" + TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(), "Unable to get counts of unallocated files for datasource, providing empty results"); } @@ -116,9 +152,9 @@ final class DataSourceInfoUtilities { * @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(), + return getCountOfTskFiles(currentDataSource, + "meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue() + + " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(), "Unable to get count of directories for datasource, providing empty results"); } @@ -130,8 +166,9 @@ final class DataSourceInfoUtilities { * @return The count. */ static Long getCountOfSlackFiles(DataSource currentDataSource) { - return getCountOfFiles(currentDataSource, - "type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(), + return getCountOfRegularFiles(currentDataSource, + getMetaFlagsContainsStatement(TSK_FS_META_FLAG_ENUM.UNALLOC) + + " AND type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(), "Unable to get count of slack files for datasources, providing empty results"); } @@ -183,9 +220,11 @@ final class DataSourceInfoUtilities { 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() + + " FROM tsk_files" + + " WHERE " + getMetaFlagsContainsStatement(TSK_FS_META_FLAG_ENUM.UNALLOC) + + " AND type<>" + TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + + " AND 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(); @@ -700,10 +739,12 @@ final class DataSourceInfoUtilities { */ static Map getCountsOfFiles() { try { - final String countFilesQuery = "data_source_obj_id, COUNT(*) AS value" - + " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() + final String countFilesQuery = "data_source_obj_id, COUNT(*) AS value FROM tsk_files" + + " WHERE meta_type=" + TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + + " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS + + " AND name<>''" + + " GROUP BY data_source_obj_id"; //NON-NLS return getValuesMap(countFilesQuery); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.WARNING, "Unable to get counts of files for all datasources, providing empty results", ex); @@ -776,21 +817,75 @@ final class DataSourceInfoUtilities { * source, null if no count was retrieved */ static Long getCountOfFilesForMimeTypes(DataSource currentDataSource, Set setOfMimeTypes) { - if (currentDataSource != null) { - try { - String inClause = String.join("', '", setOfMimeTypes); - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - return skCase.countFilesWhere("data_source_obj_id=" + currentDataSource.getId() - + " AND type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType() - + " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue() - + " AND mime_type IN ('" + inClause + "')" - + " AND name<>''"); - } catch (TskCoreException | NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get count of files for specified mime types", ex); - //unable to get count of files for the specified mimetypes cell will be displayed as empty - } - } - return null; + return getCountOfRegularFiles(currentDataSource, + "mime_type IN " + getSqlSet(setOfMimeTypes), + "Unable to get count of files for specified mime types"); + } + + /** + * Get the number of files in the case database for the current data source + * which do not have the specified mimetypes. + * + * @param currentDataSource the data source which we are finding a file + * count + * + * @param setOfMimeTypes the set of mime types that should be excluded. + * + * @return a Long value which represents the number of files that do not + * have the specific mime type, but do have a mime type. + */ + static Long getCountOfFilesNotInMimeTypes(DataSource currentDataSource, Set setOfMimeTypes) { + return getCountOfRegularFiles(currentDataSource, + "mime_type NOT IN " + getSqlSet(setOfMimeTypes) + + " AND mime_type IS NOT NULL AND mime_type <> '' ", + "Unable to get count of files without specified mime types"); + } + + /** + * Gets the number of files in the data source with no assigned mime type. + * + * @param currentDataSource The data source. + * + * @return The number of files with no mime type or null if there is an + * issue searching the data source. + * + */ + static Long getCountOfFilesWithNoMimeType(DataSource currentDataSource) { + return getCountOfRegularFiles(currentDataSource, + "(mime_type IS NULL OR mime_type = '') ", + "Unable to get count of files without a mime type"); + } + + /** + * Derives a sql set string (i.e. "('val1', 'val2', 'val3')"). A naive + * attempt is made to sanitize the strings by removing single quotes from + * values. + * + * @param setValues The values that should be present in the set. Single + * quotes are removed. + * + * @return The sql set string. + */ + private static String getSqlSet(Set setValues) { + List quotedValues = setValues + .stream() + .map(str -> String.format("'%s'", str.replace("'", ""))) + .collect(Collectors.toList()); + + String commaSeparatedQuoted = String.join(", ", quotedValues); + return String.format("(%s) ", commaSeparatedQuoted); + } + + /** + * Creates sql where clause that does a bitwise check to see if flag is + * present. + * + * @param flag The flag for which to check. + * + * @return The clause. + */ + private static String getMetaFlagsContainsStatement(TSK_FS_META_FLAG_ENUM flag) { + return "meta_flags & " + flag.getValue() + " > 0"; } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form index 9a82746da6..f922bf9288 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.form @@ -1,6 +1,6 @@ -
+ @@ -16,104 +16,206 @@ - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + - + - + + - - - - - - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java index 6bb6603c11..1c3e93c47c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryCountsPanel.java @@ -24,7 +24,6 @@ import javax.swing.JLabel; import javax.swing.table.DefaultTableCellRenderer; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.FileTypeUtils; import org.sleuthkit.datamodel.DataSource; /** @@ -39,7 +38,8 @@ import org.sleuthkit.datamodel.DataSource; "DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count" }) class DataSourceSummaryCountsPanel extends javax.swing.JPanel { - + private static final long serialVersionUID = 1L; + // Result returned for a data model if no data found. private static final Object[][] EMPTY_PAIRS = new Object[][]{}; @@ -61,9 +61,10 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_count_header() }; - private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataSourceSummaryCountsPanel.class.getName()); private final DefaultTableCellRenderer rightAlignedRenderer = new DefaultTableCellRenderer(); + + private final FileTypePieChart fileTypePieChart = new FileTypePieChart(); private DataSource dataSource; @@ -73,7 +74,6 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { DataSourceSummaryCountsPanel() { rightAlignedRenderer.setHorizontalAlignment(JLabel.RIGHT); initComponents(); - fileCountsByMimeTypeTable.getTableHeader().setReorderingAllowed(false); fileCountsByCategoryTable.getTableHeader().setReorderingAllowed(false); artifactCountsTable.getTableHeader().setReorderingAllowed(false); setDataSource(null); @@ -96,29 +96,20 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; if (dataSource == null || !Case.isCaseOpen()) { - updateCountsTableData(EMPTY_PAIRS, - EMPTY_PAIRS, - EMPTY_PAIRS); + updateCountsTableData(EMPTY_PAIRS, EMPTY_PAIRS); } else { - updateCountsTableData(getMimeTypeModel(dataSource), - getFileCategoryModel(dataSource), - getArtifactCountsModel(dataSource)); + updateCountsTableData(getFileCategoryModel(dataSource), getArtifactCountsModel(dataSource)); } - + this.fileTypePieChart.setDataSource(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); - + private void updateCountsTableData(Object[][] fileCategoryDataModel, Object[][] artifactDataModel) { fileCountsByCategoryTable.setModel(new NonEditableTableModel(fileCategoryDataModel, FILE_BY_CATEGORY_COLUMN_HEADERS)); fileCountsByCategoryTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); fileCountsByCategoryTable.getColumnModel().getColumn(0).setPreferredWidth(130); @@ -130,48 +121,6 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { this.repaint(); } - /** - * Determines the JTable data model for datasource mime types. - * - * @param dataSource The DataSource. - * - * @return The model to be used with a JTable. - */ - @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. * @@ -189,7 +138,7 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { 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 allocatedFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfAllocatedFiles(selectedDataSource)); Long slackFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfSlackFiles(selectedDataSource)); Long directories = zeroIfNull(DataSourceInfoUtilities.getCountOfDirectories(selectedDataSource)); @@ -213,24 +162,6 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { 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; - } - } - /** * The counts of different artifact types found in a DataSource. * @@ -260,83 +191,120 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { - fileCountsByMimeTypeScrollPane = new javax.swing.JScrollPane(); - fileCountsByMimeTypeTable = new javax.swing.JTable(); - byMimeTypeLabel = new javax.swing.JLabel(); - fileCountsByCategoryScrollPane = new javax.swing.JScrollPane(); + javax.swing.JScrollPane scrollParent = new javax.swing.JScrollPane(); + javax.swing.JPanel parentPanel = new javax.swing.JPanel(); + javax.swing.JScrollPane fileCountsByCategoryScrollPane = new javax.swing.JScrollPane(); fileCountsByCategoryTable = new javax.swing.JTable(); - byCategoryLabel = new javax.swing.JLabel(); - jLabel1 = new javax.swing.JLabel(); - artifactCountsScrollPane = new javax.swing.JScrollPane(); + javax.swing.JLabel byCategoryLabel = new javax.swing.JLabel(); + javax.swing.JLabel resultsByTypeLabel = new javax.swing.JLabel(); + javax.swing.JScrollPane artifactCountsScrollPane = new javax.swing.JScrollPane(); artifactCountsTable = new javax.swing.JTable(); + javax.swing.JPanel fileTypePiePanel = fileTypePieChart; + javax.swing.JPanel filesByCatParent = new javax.swing.JPanel(); + javax.swing.JPanel resultsByTypeParent = new javax.swing.JPanel(); - fileCountsByMimeTypeScrollPane.setViewportView(fileCountsByMimeTypeTable); - - org.openide.awt.Mnemonics.setLocalizedText(byMimeTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byMimeTypeLabel.text")); // NOI18N + parentPanel.setMinimumSize(new java.awt.Dimension(840, 320)); 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 + org.openide.awt.Mnemonics.setLocalizedText(resultsByTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.resultsByTypeLabel.text")); // NOI18N + artifactCountsTable.setAutoCreateRowSorter(true); artifactCountsScrollPane.setViewportView(artifactCountsTable); + fileTypePiePanel.setPreferredSize(new java.awt.Dimension(400, 300)); + + javax.swing.GroupLayout filesByCatParentLayout = new javax.swing.GroupLayout(filesByCatParent); + filesByCatParent.setLayout(filesByCatParentLayout); + filesByCatParentLayout.setHorizontalGroup( + filesByCatParentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + filesByCatParentLayout.setVerticalGroup( + filesByCatParentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout resultsByTypeParentLayout = new javax.swing.GroupLayout(resultsByTypeParent); + resultsByTypeParent.setLayout(resultsByTypeParentLayout); + resultsByTypeParentLayout.setHorizontalGroup( + resultsByTypeParentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + resultsByTypeParentLayout.setVerticalGroup( + resultsByTypeParentLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout parentPanelLayout = new javax.swing.GroupLayout(parentPanel); + parentPanel.setLayout(parentPanelLayout); + parentPanelLayout.setHorizontalGroup( + parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(parentPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(fileTypePiePanel, javax.swing.GroupLayout.PREFERRED_SIZE, 400, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fileCountsByCategoryScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(byCategoryLabel) + .addComponent(filesByCatParent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(18, 18, 18) + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(resultsByTypeLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(parentPanelLayout.createSequentialGroup() + .addComponent(artifactCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 244, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(resultsByTypeParent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + parentPanelLayout.setVerticalGroup( + parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(parentPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(parentPanelLayout.createSequentialGroup() + .addComponent(fileTypePiePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(parentPanelLayout.createSequentialGroup() + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(byCategoryLabel) + .addComponent(resultsByTypeLabel)) + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, parentPanelLayout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultsByTypeParent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(148, 148, 148)) + .addGroup(parentPanelLayout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(parentPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(artifactCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(parentPanelLayout.createSequentialGroup() + .addComponent(fileCountsByCategoryScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(31, 31, 31) + .addComponent(filesByCatParent, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()))))) + ); + + scrollParent.setViewportView(parentPanel); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(fileCountsByMimeTypeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 140, Short.MAX_VALUE) - .addComponent(byMimeTypeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(byCategoryLabel) - .addComponent(fileCountsByCategoryScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(artifactCountsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 244, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(scrollParent) ); - - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {fileCountsByCategoryScrollPane, fileCountsByMimeTypeScrollPane}); - layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(byMimeTypeLabel) - .addComponent(byCategoryLabel) - .addComponent(jLabel1)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(artifactCountsScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(fileCountsByMimeTypeScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(fileCountsByCategoryScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 86, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()) + .addComponent(scrollParent) ); - - layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {fileCountsByCategoryScrollPane, fileCountsByMimeTypeScrollPane}); - }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane artifactCountsScrollPane; private javax.swing.JTable artifactCountsTable; - private javax.swing.JLabel byCategoryLabel; - private javax.swing.JLabel byMimeTypeLabel; - private javax.swing.JScrollPane fileCountsByCategoryScrollPane; private javax.swing.JTable fileCountsByCategoryTable; - private javax.swing.JScrollPane fileCountsByMimeTypeScrollPane; - private javax.swing.JTable fileCountsByMimeTypeTable; - private javax.swing.JLabel jLabel1; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form index 272fc9b041..2ee3ae314a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.form @@ -22,7 +22,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java index 9cd7549c06..19a1fb093e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java @@ -125,7 +125,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(dataSourceSummarySplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 668, Short.MAX_VALUE) + .addComponent(dataSourceSummarySplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 860, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addComponent(closeButton))) @@ -135,7 +135,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() - .addComponent(dataSourceSummarySplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 362, Short.MAX_VALUE) + .addComponent(dataSourceSummarySplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 540, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(closeButton) .addContainerGap()) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/FileTypePieChart.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/FileTypePieChart.java new file mode 100644 index 0000000000..f7700e2ce3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/FileTypePieChart.java @@ -0,0 +1,188 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit 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 java.awt.BorderLayout; +import java.awt.Font; +import javax.swing.JPanel; +import org.sleuthkit.datamodel.DataSource; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.labels.PieSectionLabelGenerator; +import org.jfree.chart.labels.StandardPieSectionLabelGenerator; +import org.jfree.chart.plot.PiePlot; +import org.jfree.data.general.DefaultPieDataset; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.swing.JLabel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.FileTypeUtils; +import static org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; + +/** + * A Pie Chart that shows file mime types in a data source. + */ +class FileTypePieChart extends JPanel { + private static final long serialVersionUID = 1L; + + private static final Font DEFAULT_FONT = new JLabel().getFont(); + private static final Font DEFAULT_HEADER_FONT = new Font(DEFAULT_FONT.getName(), DEFAULT_FONT.getStyle(), (int) (DEFAULT_FONT.getSize() * 1.5)); + + private final DefaultPieDataset dataset = new DefaultPieDataset(); + private DataSource dataSource; + + // used for determining mime types that fall in the 'other' category + private static final Set ALL_CATEGORY_MIME_TYPES = Arrays.asList( + FileTypeCategory.IMAGE, + FileTypeCategory.VIDEO, + FileTypeCategory.AUDIO, + FileTypeCategory.DOCUMENTS, + FileTypeCategory.EXECUTABLE) + .stream() + .flatMap((cat) -> cat.getMediaTypes().stream()) + .collect(Collectors.toSet()); + + /** + * Default constructor for the pie chart. + */ + @Messages({ + "DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images", + "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos", + "DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_other_label=Other", + "DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_notAnalyzed_label=Not Analyzed" + }) + FileTypePieChart() { + // Create chart + JFreeChart chart = ChartFactory.createPieChart( + Bundle.DataSourceSummaryCountsPanel_byMimeTypeLabel_text(), + dataset, + true, + true, + false); + + chart.setBackgroundPaint(null); + chart.getLegend().setItemFont(DEFAULT_FONT); + chart.getTitle().setFont(DEFAULT_HEADER_FONT); + + PiePlot plot = ((PiePlot) chart.getPlot()); + + //Format Label + PieSectionLabelGenerator labelGenerator = new StandardPieSectionLabelGenerator( + "{0}: {1} ({2})", new DecimalFormat("0"), new DecimalFormat("0.0%")); + + plot.setLabelGenerator(labelGenerator); + plot.setLabelFont(DEFAULT_FONT); + + plot.setBackgroundPaint(null); + plot.setOutlinePaint(null); + + // Create Panel + ChartPanel panel = new ChartPanel(chart); + this.setLayout(new BorderLayout()); + this.add(panel, BorderLayout.CENTER); + } + + /** + * The datasource currently used as the model with this pie chart. + * + * @return The datasource currently being used as the model in this pie + * chart. + */ + DataSource getDataSource() { + return dataSource; + } + + /** + * Sets datasource to visualize in the pie chart. + * + * @param dataSource The datasource to use in this pie chart. + */ + void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + this.dataset.clear(); + + if (dataSource != null) { + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row(), + this.dataSource, FileTypeCategory.IMAGE); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row(), + this.dataSource, FileTypeCategory.VIDEO); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row(), + this.dataSource, FileTypeCategory.AUDIO); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row(), + this.dataSource, FileTypeCategory.DOCUMENTS); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row(), + this.dataSource, FileTypeCategory.EXECUTABLE); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_other_label(), + DataSourceInfoUtilities.getCountOfFilesNotInMimeTypes(this.dataSource, ALL_CATEGORY_MIME_TYPES)); + addIfPresent(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_notAnalyzed_label(), + DataSourceInfoUtilities.getCountOfFilesWithNoMimeType(this.dataSource)); + } + } + + /** + * Adds count for file type category if there is a value. Uses fields + * 'dataSource' and 'dataset'. + * + * @param label The label for this pie slice. + * @param dataSource The data source. + * @param category The category for the pie slice. + */ + private void addIfPresent(String label, DataSource dataSource, FileTypeUtils.FileTypeCategory category) { + if (dataSource == null) { + return; + } + + Long count = getCount(dataSource, category); + addIfPresent(label, count); + } + + /** + * Adds count for a a label if the count is non-null and greater than 0. + * + * @param label The label. + * @param count The count. + */ + private void addIfPresent(String label, Long count) { + if (count != null && count > 0) { + this.dataset.setValue(label, count); + } + } + + /** + * 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()); + } +}