From 661d6a5dbd777491681425eec1925b1707ec13c4 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 12 Mar 2021 16:46:03 -0500 Subject: [PATCH 01/14] excel export start --- .../uiutils/ExcelExport.java | 54 ++++++++++++++++++ .../uiutils/ExcelTableExport.java | 57 +------------------ 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java index cf4869ce47..b453fd664d 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java @@ -21,9 +21,14 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.Optional; +import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; @@ -190,4 +195,53 @@ public class ExcelExport { // Closing the workbook workbook.close(); } + + + /** + * Create a cell style in the workbook with the given format string. + * + * @param workbook The workbook. + * @param formatString The format string. + * @return The cell style. + */ + static CellStyle createCellStyle(Workbook workbook, String formatString) { + CellStyle cellStyle = workbook.createCellStyle(); + cellStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(formatString)); + return cellStyle; + } + + /** + * Creates an excel cell given the model. + * + * @param row The row in the excel document. + * @param colNum The column number (not zero-indexed). + * @param cellModel The model for the cell. + * @param cellStyle The style to use. + * @return The created cell. + */ + static Cell createCell(Row row, int colNum, ExcelTableExport.ExcelCellModel cellModel, Optional cellStyle) { + Object cellData = cellModel.getData(); + Cell cell = row.createCell(colNum); + if (cellData instanceof Calendar) { + cell.setCellValue((Calendar) cellData); + } else if (cellData instanceof Date) { + cell.setCellValue((Date) cellData); + } else if (cellData instanceof Double) { + cell.setCellValue((Double) cellData); + } else if (cellData instanceof String) { + cell.setCellValue((String) cellData); + } else if (cellData instanceof Short) { + cell.setCellValue((Short) cellData); + } else if (cellData instanceof Integer) { + cell.setCellValue((Integer) cellData); + } else if (cellData instanceof Long) { + cell.setCellValue((Long) cellData); + } else if (cellData instanceof Float) { + cell.setCellValue((Float) cellData); + } else { + cell.setCellValue(cellModel.getText()); + } + cellStyle.ifPresent(cs -> cell.setCellStyle(cs)); + return cell; + } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index 43b8320c49..de1e3f684c 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.datasourcesummary.uiutils; -import java.util.Calendar; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,7 +27,7 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; /** @@ -116,58 +114,9 @@ public class ExcelTableExport implements ExcelExpor String formatString = cellModel.getExcelFormatString(); Optional cellStyle = (formatString == null) ? Optional.empty() - : Optional.of(cellStyles.computeIfAbsent(formatString, k -> createCellStyle(worksheetEnv.getParentWorkbook(), formatString))); - createCell(row, colNum, cellModel, cellStyle); + : Optional.of(cellStyles.computeIfAbsent(formatString, k -> ExcelExport.createCellStyle(worksheetEnv.getParentWorkbook(), formatString))); + ExcelExport.createCell(row, colNum, cellModel, cellStyle); } } } - - /** - * Create a cell style in the workbook with the given format string. - * - * @param workbook The workbook. - * @param formatString The format string. - * @return The cell style. - */ - private static CellStyle createCellStyle(Workbook workbook, String formatString) { - CellStyle cellStyle = workbook.createCellStyle(); - cellStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(formatString)); - return cellStyle; - } - - /** - * Creates an excel cell given the model. - * - * @param row The row in the excel document. - * @param colNum The column number (not zero-indexed). - * @param cellModel The model for the cell. - * @param cellStyle The style to use. - * @return The created cell. - */ - private static Cell createCell(Row row, int colNum, ExcelCellModel cellModel, Optional cellStyle) { - Object cellData = cellModel.getData(); - Cell cell = row.createCell(colNum); - if (cellData instanceof Calendar) { - cell.setCellValue((Calendar) cellData); - } else if (cellData instanceof Date) { - cell.setCellValue((Date) cellData); - } else if (cellData instanceof Double) { - cell.setCellValue((Double) cellData); - } else if (cellData instanceof String) { - cell.setCellValue((String) cellData); - } else if (cellData instanceof Short) { - cell.setCellValue((Short) cellData); - } else if (cellData instanceof Integer) { - cell.setCellValue((Integer) cellData); - } else if (cellData instanceof Long) { - cell.setCellValue((Long) cellData); - } else if (cellData instanceof Float) { - cell.setCellValue((Float) cellData); - } else { - cell.setCellValue(cellModel.getText()); - } - cellStyle.ifPresent(cs -> cell.setCellStyle(cs)); - return cell; - } - } From cacd846888271bbd95cdc04aeba8a9a7c4e21f88 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Fri, 12 Mar 2021 21:08:34 -0500 Subject: [PATCH 02/14] add special format type --- .../uiutils/ExcelSpecialFormatExport.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java new file mode 100644 index 0000000000..139bac48b4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -0,0 +1,109 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.datasourcesummary.uiutils; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; + +/** + * + * @author gregd + */ +public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { + public static class ExcelItemResult { + private final int rowStart; + private final int rowEnd; + private final int colStart; + private final int colEnd; + + public ExcelItemResult(int rowStart, int rowEnd, int colStart, int colEnd) { + this.rowStart = rowStart; + this.rowEnd = rowEnd; + this.colStart = colStart; + this.colEnd = colEnd; + } + + public int getRowStart() { + return rowStart; + } + + public int getRowEnd() { + return rowEnd; + } + + public int getColStart() { + return colStart; + } + + public int getColEnd() { + return colEnd; + } + } + + + public interface ExcelItemExportable { + int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; + } + + public static class KeyValueItemExportable implements ExcelItemExportable { + private final ExcelCellModel key; + private final ExcelCellModel value; + private final int colStart; + + public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) { + this(key, value, 1); + } + + public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value, int colStart) { + this.key = key; + this.value = value; + this.colStart = colStart; + } + + @Override + public int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + Row row = sheet.getRow(rowStart); + ExcelExport.createCell(row, colStart, key, Optional.of(env.getHeaderStyle())); + ExcelExport.createCell(row, colStart + 1, value, Optional.empty()); + return rowStart + 1; + } + + } + + private final String sheetName; + private final List exports; + + public ExcelSpecialFormatExport(String sheetName, List exports) { + this.sheetName = sheetName; + this.exports = exports == null ? Collections.emptyList() : exports; + } + + + + @Override + public String getSheetName() { + return sheetName; + } + + @Override + public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) throws ExcelExportException { + int rowStart = 1; + for (ExcelItemExportable export : exports) { + if (export == null) { + continue; + } + + int endRow = export.write(sheet, rowStart, env); + rowStart = endRow + 1; + } + } + +} From d3e5dcc1d5bd0d8fa7ebdb72ce29acf34bd497ed Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Sun, 14 Mar 2021 20:45:56 -0400 Subject: [PATCH 03/14] beginning to use with container panel --- .../datasourcesummary/ui/ContainerPanel.java | 40 ++++++++++++++--- .../uiutils/ExcelTableExport.java | 43 +++++++++++++++---- 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index c19fd2198e..53af00adce 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -30,10 +30,16 @@ import javax.swing.table.DefaultTableModel; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary; +import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult; +import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getTableExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Image; @@ -42,6 +48,9 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Panel to display additional details associated with a specific DataSource */ +@Messages({ + "ContainerPanel_tabName=Container" +}) class ContainerPanel extends BaseDataSourceSummaryPanel { /** @@ -103,6 +112,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { private static final Logger logger = Logger.getLogger(ContainerPanel.class.getName()); private final List> dataFetchComponents; + private final DataFetcher containerDataFetcher; /** * Creates a new form ContainerPanel. @@ -117,14 +127,16 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ContainerPanel(ContainerSummary containerSummary) { super(containerSummary, CONTAINER_UPDATES); + containerDataFetcher = (dataSource) -> { + return new ContainerPanelData( + dataSource, + containerSummary.getSizeOfUnallocatedFiles(dataSource) + ); + }; + dataFetchComponents = Arrays.asList( new DataFetchComponents<>( - (dataSource) -> { - return new ContainerPanelData( - dataSource, - containerSummary.getSizeOfUnallocatedFiles(dataSource) - ); - }, + containerDataFetcher, (result) -> { if (result != null && result.getResultType() == ResultType.SUCCESS) { ContainerPanelData data = result.getData(); @@ -279,7 +291,21 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { @Override List getExports(DataSource ds) { - return Collections.emptyList(); + ContainerPanelData result = getFetchResult(containerDataFetcher, "Container sheets", ds); + if (ds == null || result == null) { + return Collections.emptyList(); + } + + return Arrays.asList( + new ExcelSpecialFormatExport(Bundle.ContainerPanel_tabName(), Arrays.asList( + new KeyValueItemExportable(Bundle.ContainerPanel_export_displayName(), ds.getName()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), ds.getName()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), ds.getDeviceId()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), ds.getTimeZone()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_acquisitionDetails(), null), + // ...acquisition details... + )) + ); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index de1e3f684c..bdbd475de0 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -28,12 +28,14 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; /** * An excel sheet export of table data. */ -public class ExcelTableExport implements ExcelExport.ExcelSheetExport { +public class ExcelTableExport implements ExcelSheetExport, ExcelItemExportable { /** * Basic interface for a cell model. @@ -50,6 +52,7 @@ public class ExcelTableExport implements ExcelExpor private final String sheetName; private final List> columns; private final List data; + private final int colStart; /** * Main constructor. @@ -60,9 +63,14 @@ public class ExcelTableExport implements ExcelExpor * @param data The data to export. */ public ExcelTableExport(String sheetName, List> columns, List data) { + this(sheetName, columns, data, 0); + } + + public ExcelTableExport(String sheetName, List> columns, List data, int colStart) { this.sheetName = sheetName; this.columns = columns; this.data = data; + this.colStart = colStart; } @Override @@ -72,11 +80,14 @@ public class ExcelTableExport implements ExcelExpor @Override public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException { - renderSheet(sheet, style, columns, data); - // Resize all columns to fit the content size - for (int i = 0; i < columns.size(); i++) { - sheet.autoSizeColumn(i); - } + renderSheet(sheet, style, 1, colStart, columns, data); + + } + + @Override + public int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + int rowsWritten = renderSheet(sheet, env, rowStart, colStart, columns, data); + return rowStart + rowsWritten - 1; } /** @@ -84,12 +95,19 @@ public class ExcelTableExport implements ExcelExpor * * @param sheet The sheet. * @param worksheetEnv The worksheet environment and preferences. + * @param rowStart The row to start in. + * @param colStart The column to start in. * @param columns The columns. * @param data The data. * @throws ExcelExportException + * @return The number of rows (including the header) written. */ - private static void renderSheet( - Sheet sheet, ExcelExport.WorksheetEnv worksheetEnv, List> columns, List data) + private static int renderSheet( + Sheet sheet, + ExcelExport.WorksheetEnv worksheetEnv, + int rowStart, + int colStart, + List> columns, List data) throws ExcelExport.ExcelExportException { List safeData = data == null ? Collections.emptyList() : data; @@ -115,8 +133,15 @@ public class ExcelTableExport implements ExcelExpor Optional cellStyle = (formatString == null) ? Optional.empty() : Optional.of(cellStyles.computeIfAbsent(formatString, k -> ExcelExport.createCellStyle(worksheetEnv.getParentWorkbook(), formatString))); - ExcelExport.createCell(row, colNum, cellModel, cellStyle); + ExcelExport.createCell(row, colNum + colStart, cellModel, cellStyle); } } + + // Resize all columns to fit the content size + for (int i = colStart; i < columns.size() + colStart; i++) { + sheet.autoSizeColumn(i); + } + + return safeData.size() + 1; } } From 9fdfac8d5ae366989b4e6931f0701d63baf66eed Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 15 Mar 2021 09:56:53 -0400 Subject: [PATCH 04/14] working through container tab --- .../datasourcesummary/ui/ContainerPanel.java | 46 ++++++++++++-- .../uiutils/ExcelSpecialFormatExport.java | 60 ++++++++++++++----- .../uiutils/ExcelTableExport.java | 10 +--- 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index 53af00adce..df70262391 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -25,21 +25,24 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.table.DefaultTableModel; +import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; -import org.sleuthkit.autopsy.datasourcesummary.datamodel.PastCasesSummary; import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult; -import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getTableExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.TitledExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.Image; @@ -289,6 +292,31 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ((DefaultTableModel) filePathsTable.getModel()).setRowCount(0); } + + private static List getAcquisitionDetails(String acquisitionDetails) { + if (StringUtils.isBlank(acquisitionDetails)) { + return Collections.emptyList(); + } else { + return Stream.of(acquisitionDetails.split("\\r?\\n")) + .map((line) -> { + if (StringUtils.isBlank(line)) { + return null; + } else { + int colonIdx = line.indexOf(':'); + if (colonIdx >= 0) { + return new KeyValueItemExportable(new DefaultCellModel<>(line.substring(0, colonIdx + 1).trim()), + new DefaultCellModel<>(line.substring(colonIdx + 1, line.length()).trim())); + } else { + return new KeyValueItemExportable(new DefaultCellModel<>(""), new DefaultCellModel<>(line)); + } + } + }) + .filter(item -> item != null) + .collect(Collectors.toList()); + } + } + + @Override List getExports(DataSource ds) { ContainerPanelData result = getFetchResult(containerDataFetcher, "Container sheets", ds); @@ -302,8 +330,18 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), ds.getName()), new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), ds.getDeviceId()), new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), ds.getTimeZone()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_acquisitionDetails(), null), - // ...acquisition details... + + new TitledExportable(Bundle.ContainerPanel_export_acquisitionDetails(), getAcquisitionDetails(ds.getAcquisitionDetails())), + + new KeyValueItemExportable() + imageTypeValue.setText(""); + sizeValue.setText(""); + sectorSizeValue.setText(""); + md5HashValue.setText(""); + sha1HashValue.setText(""); + sha256HashValue.setText(""); + unallocatedSizeValue.setText(""); + ((DefaultTableModel) filePathsTable.getModel()).setRowCount(0); )) ); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java index 139bac48b4..f8c1a7a082 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -18,7 +18,9 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCel * @author gregd */ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { + public static class ExcelItemResult { + private final int rowStart; private final int rowEnd; private final int colStart; @@ -47,13 +49,14 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { return colEnd; } } - - + public interface ExcelItemExportable { - int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; + + int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; } - + public static class KeyValueItemExportable implements ExcelItemExportable { + private final ExcelCellModel key; private final ExcelCellModel value; private final int colStart; @@ -61,23 +64,22 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) { this(key, value, 1); } - + public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value, int colStart) { this.key = key; this.value = value; this.colStart = colStart; } - + @Override - public int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { Row row = sheet.getRow(rowStart); ExcelExport.createCell(row, colStart, key, Optional.of(env.getHeaderStyle())); ExcelExport.createCell(row, colStart + 1, value, Optional.empty()); return rowStart + 1; } - } - + private final String sheetName; private final List exports; @@ -85,9 +87,37 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { this.sheetName = sheetName; this.exports = exports == null ? Collections.emptyList() : exports; } - - - + + public static class TitledExportable implements ExcelItemExportable { + + private static final int DEFAULT_INDENT = 1; + + private final String title; + private final List children; + + public TitledExportable(String title, List children) { + this.title = title; + this.children = children; + } + + @Override + public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + ExcelExport.createCell(sheet.getRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle())); + int curRow = rowStart + 1; + for (ExcelItemExportable export : children) { + if (export == null) { + continue; + } + + int endRow = export.write(sheet, rowStart, colStart + DEFAULT_INDENT, env); + curRow = endRow + 1; + } + + return curRow; + } + + } + @Override public String getSheetName() { return sheetName; @@ -100,10 +130,10 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { if (export == null) { continue; } - - int endRow = export.write(sheet, rowStart, env); + + int endRow = export.write(sheet, rowStart, 1, env); rowStart = endRow + 1; } } - + } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index bdbd475de0..6f108af9ae 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -52,7 +52,6 @@ public class ExcelTableExport implements ExcelSheet private final String sheetName; private final List> columns; private final List data; - private final int colStart; /** * Main constructor. @@ -63,14 +62,9 @@ public class ExcelTableExport implements ExcelSheet * @param data The data to export. */ public ExcelTableExport(String sheetName, List> columns, List data) { - this(sheetName, columns, data, 0); - } - - public ExcelTableExport(String sheetName, List> columns, List data, int colStart) { this.sheetName = sheetName; this.columns = columns; this.data = data; - this.colStart = colStart; } @Override @@ -80,12 +74,12 @@ public class ExcelTableExport implements ExcelSheet @Override public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException { - renderSheet(sheet, style, 1, colStart, columns, data); + renderSheet(sheet, style, 1, 1, columns, data); } @Override - public int write(Sheet sheet, int rowStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { int rowsWritten = renderSheet(sheet, env, rowStart, colStart, columns, data); return rowStart + rowsWritten - 1; } From 67f33ba57ccdf7aa475deaed3d420a2c9728cbc5 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 15 Mar 2021 19:55:23 -0400 Subject: [PATCH 05/14] working through container panel --- .../datasourcesummary/ui/ContainerPanel.java | 348 +++++++++++------- .../ui/SizeRepresentationUtil.java | 99 +++-- .../uiutils/ExcelSpecialFormatExport.java | 30 +- 3 files changed, 308 insertions(+), 169 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index df70262391..02432f9e0e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.datasourcesummary.ui; import java.beans.PropertyChangeEvent; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -33,15 +35,18 @@ import org.apache.commons.lang.StringUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult; +import org.sleuthkit.autopsy.datasourcesummary.ui.SizeRepresentationUtil.SizeUnit; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.SingleCellExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.TitledExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.UpdateGovernor; import org.sleuthkit.datamodel.DataSource; @@ -56,37 +61,107 @@ import org.sleuthkit.datamodel.TskCoreException; }) class ContainerPanel extends BaseDataSourceSummaryPanel { - /** - * Data payload for the Container panel. - */ - private static class ContainerPanelData { + private static class ImageViewModel { - private final DataSource dataSource; - private final Long unallocatedFilesSize; + private final long unallocatedSize; + private final long size; + private final long sectorSize; - /** - * Main constructor. - * - * @param dataSource The original datasource. - * @param unallocatedFilesSize The unallocated file size. - */ - ContainerPanelData(DataSource dataSource, Long unallocatedFilesSize) { - this.dataSource = dataSource; - this.unallocatedFilesSize = unallocatedFilesSize; + private final String timeZone; + private final String imageType; + + private final List paths; + private final String md5Hash; + private final String sha1Hash; + private final String sha256Hash; + + public ImageViewModel(long unallocatedSize, long size, long sectorSize, + String timeZone, String imageType, List paths, String md5Hash, + String sha1Hash, String sha256Hash) { + this.unallocatedSize = unallocatedSize; + this.size = size; + this.sectorSize = sectorSize; + this.timeZone = timeZone; + this.imageType = imageType; + this.paths = paths == null ? Collections.emptyList() : new ArrayList<>(paths); + this.md5Hash = md5Hash; + this.sha1Hash = sha1Hash; + this.sha256Hash = sha256Hash; } - /** - * @return The original datasource. - */ - DataSource getDataSource() { - return dataSource; + public long getUnallocatedSize() { + return unallocatedSize; } - /** - * @return The unallocated file size. - */ - Long getUnallocatedFilesSize() { - return unallocatedFilesSize; + public long getSize() { + return size; + } + + public long getSectorSize() { + return sectorSize; + } + + public String getTimeZone() { + return timeZone; + } + + public String getImageType() { + return imageType; + } + + public List getPaths() { + return paths; + } + + public String getMd5Hash() { + return md5Hash; + } + + public String getSha1Hash() { + return sha1Hash; + } + + public String getSha256Hash() { + return sha256Hash; + } + + } + + private static class ContainerViewModel { + + private final String displayName; + private final String originalName; + private final String deviceIdValue; + private final String acquisitionDetails; + private final ImageViewModel imageViewModel; + + ContainerViewModel(String displayName, String originalName, String deviceIdValue, + String acquisitionDetails, ImageViewModel imageViewModel) { + this.displayName = displayName; + this.originalName = originalName; + this.deviceIdValue = deviceIdValue; + this.acquisitionDetails = acquisitionDetails; + this.imageViewModel = imageViewModel; + } + + String getDisplayName() { + return displayName; + } + + String getOriginalName() { + return originalName; + } + + String getDeviceId() { + return deviceIdValue; + } + + String getAcquisitionDetails() { + return acquisitionDetails; + } + + ImageViewModel getImageViewModel() { + return imageViewModel; } } @@ -115,7 +190,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { private static final Logger logger = Logger.getLogger(ContainerPanel.class.getName()); private final List> dataFetchComponents; - private final DataFetcher containerDataFetcher; + private final DataFetcher containerDataFetcher; /** * Creates a new form ContainerPanel. @@ -130,23 +205,15 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ContainerPanel(ContainerSummary containerSummary) { super(containerSummary, CONTAINER_UPDATES); - containerDataFetcher = (dataSource) -> { - return new ContainerPanelData( - dataSource, - containerSummary.getSizeOfUnallocatedFiles(dataSource) - ); - }; + containerDataFetcher = (dataSource) -> getContainerViewModel(containerSummary, dataSource); dataFetchComponents = Arrays.asList( new DataFetchComponents<>( containerDataFetcher, (result) -> { if (result != null && result.getResultType() == ResultType.SUCCESS) { - ContainerPanelData data = result.getData(); - DataSource dataSource = (data == null) ? null : data.getDataSource(); - Long unallocatedFileSize = (data == null) ? null : data.getUnallocatedFilesSize(); - - updateDetailsPanelData(dataSource, unallocatedFileSize); + ContainerViewModel data = result.getData(); + updateDetailsPanelData(data); } else { if (result == null) { logger.log(Level.WARNING, "No data fetch result was provided to the ContainerPanel."); @@ -154,8 +221,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { logger.log(Level.WARNING, "An exception occurred while attempting to fetch data for the ContainerPanel.", result.getException()); } - - updateDetailsPanelData(null, null); + updateDetailsPanelData(null); } } ) @@ -175,29 +241,67 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { fetchInformation(dataFetchComponents, dataSource); } - /** - * Update which DataSource this panel should display details about - * - * @param selectedDataSource the DataSource to display details about. - */ - private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize) { + private interface Retriever { + + O retrieve() throws TskCoreException, SleuthkitCaseProviderException, SQLException; + } + + private static O retrieve(Retriever retriever) { + try { + return retriever.retrieve(); + } catch (TskCoreException | SleuthkitCaseProviderException | SQLException ex) { + logger.log(Level.WARNING, "Error while retrieving data.", ex); + return null; + } + } + + private static ContainerViewModel getContainerViewModel(ContainerSummary containerSummary, DataSource ds) { + if (ds == null) { + return null; + } + + return new ContainerViewModel( + ds.getName(), + ds.getName(), + ds.getDeviceId(), + retrieve(() -> ds.getAcquisitionDetails()), + ds instanceof Image ? getImageViewModel(containerSummary, (Image) ds) : null + ); + } + + private static ImageViewModel getImageViewModel(ContainerSummary containerSummary, Image image) { + if (image == null) { + return null; + } + + Long unallocSize = retrieve(() -> containerSummary.getSizeOfUnallocatedFiles(image)); + String imageType = image.getType().getName(); + Long size = image.getSize(); + Long sectorSize = image.getSsize(); + String timeZone = image.getTimeZone(); + List paths = image.getPaths() == null ? Collections.emptyList() : Arrays.asList(image.getPaths()); + String md5 = retrieve(() -> image.getMd5()); + String sha1 = retrieve(() -> image.getSha1()); + String sha256 = retrieve(() -> image.getSha256()); + + return new ImageViewModel(unallocSize, size, sectorSize, timeZone, imageType, paths, md5, sha1, sha256); + } + + private void updateDetailsPanelData(ContainerViewModel viewModel) { clearTableValues(); - if (selectedDataSource != null) { - displayNameValue.setText(selectedDataSource.getName()); - originalNameValue.setText(selectedDataSource.getName()); - deviceIdValue.setText(selectedDataSource.getDeviceId()); + if (viewModel == null) { + return; + } - try { - acquisitionDetailsTextArea.setText(selectedDataSource.getAcquisitionDetails()); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Unable to get acquisition details for selected data source", ex); - } + displayNameValue.setText(viewModel.getDisplayName()); + originalNameValue.setText(viewModel.getOriginalName()); + deviceIdValue.setText(viewModel.getDeviceId()); + acquisitionDetailsTextArea.setText(viewModel.getAcquisitionDetails()); - if (selectedDataSource instanceof Image) { - setFieldsForImage((Image) selectedDataSource, unallocatedFilesSize); - } else { - setFieldsForNonImageDataSource(); - } + if (viewModel.getImageViewModel() != null) { + setFieldsForImage(viewModel.getImageViewModel()); + } else { + setFieldsForNonImageDataSource(); } this.repaint(); @@ -222,55 +326,20 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { sha256HashValue.setText(NA); } - /** - * 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. - * @param unallocatedFilesSize Unallocated file size in bytes. - */ - private void setFieldsForImage(Image selectedImage, Long unallocatedFilesSize) { - unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(unallocatedFilesSize)); - imageTypeValue.setText(selectedImage.getType().getName()); - sizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSize())); - sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(selectedImage.getSsize())); - timeZoneValue.setText(selectedImage.getTimeZone()); + private void setFieldsForImage(ImageViewModel viewModel) { + unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getUnallocatedSize())); + imageTypeValue.setText(viewModel.getImageType()); + sizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getSize())); + sectorSizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getSectorSize())); + timeZoneValue.setText(viewModel.getTimeZone()); - for (String path : selectedImage.getPaths()) { + for (String path : viewModel.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 = ""; - } - 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); - } 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); - } + md5HashLabel.setText(viewModel.getMd5Hash()); + sha1HashValue.setText(viewModel.getSha1Hash()); + sha256HashValue.setText(viewModel.getSha256Hash()); } /** @@ -292,7 +361,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ((DefaultTableModel) filePathsTable.getModel()).setRowCount(0); } - private static List getAcquisitionDetails(String acquisitionDetails) { if (StringUtils.isBlank(acquisitionDetails)) { return Collections.emptyList(); @@ -304,7 +372,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { } else { int colonIdx = line.indexOf(':'); if (colonIdx >= 0) { - return new KeyValueItemExportable(new DefaultCellModel<>(line.substring(0, colonIdx + 1).trim()), + return new KeyValueItemExportable(new DefaultCellModel<>(line.substring(0, colonIdx + 1).trim()), new DefaultCellModel<>(line.substring(colonIdx + 1, line.length()).trim())); } else { return new KeyValueItemExportable(new DefaultCellModel<>(""), new DefaultCellModel<>(line)); @@ -315,35 +383,59 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { .collect(Collectors.toList()); } } - + + private DefaultCellModel getBytesCell(Long bytes) { + if (bytes == null) { + return new DefaultCellModel<>(""); + } else { + SizeUnit unit = SizeRepresentationUtil.getSizeUnit(bytes); + return new DefaultCellModel<>(bytes, unit.getExcelFormatString()); + } + } @Override - List getExports(DataSource ds) { - ContainerPanelData result = getFetchResult(containerDataFetcher, "Container sheets", ds); + protected List getExports(DataSource ds) { + ContainerViewModel result = getFetchResult(containerDataFetcher, "Container sheets", ds); if (ds == null || result == null) { return Collections.emptyList(); } + String NA = Bundle.ContainerPanel_setFieldsForNonImageDataSource_na(); + DefaultCellModel NACell = new DefaultCellModel<>(NA); + + ImageViewModel imageModel = result.getImageViewModel(); + boolean hasImage = imageModel != null; + + DefaultCellModel timeZone = hasImage ? new DefaultCellModel<>(imageModel.getTimeZone()) : NACell; + DefaultCellModel imageType = hasImage ? new DefaultCellModel<>(imageModel.getImageType()) : NACell; + DefaultCellModel size = hasImage ? getBytesCell(imageModel.getSize()) : NACell; + DefaultCellModel sectorSize = hasImage ? getBytesCell(imageModel.getSectorSize()) : NACell; + DefaultCellModel md5 = hasImage ? new DefaultCellModel<>(imageModel.getMd5Hash()) : NACell; + DefaultCellModel sha1 = hasImage ? new DefaultCellModel<>(imageModel.getSha1Hash()) : NACell; + DefaultCellModel sha256 = hasImage ? new DefaultCellModel<>(imageModel.getSha256Hash()) : NACell; + DefaultCellModel unallocatedSize = hasImage ? getBytesCell(imageModel.getUnallocatedSize()) : NACell; + List paths = result.getImageViewModel() == null ? Collections.singletonList(NA) : result.getImageViewModel().getPaths(); + List cellPaths = paths.stream() + .map(SingleCellExportable::new) + .collect(Collectors.toList()); + return Arrays.asList( new ExcelSpecialFormatExport(Bundle.ContainerPanel_tabName(), Arrays.asList( - new KeyValueItemExportable(Bundle.ContainerPanel_export_displayName(), ds.getName()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), ds.getName()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), ds.getDeviceId()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), ds.getTimeZone()), - - new TitledExportable(Bundle.ContainerPanel_export_acquisitionDetails(), getAcquisitionDetails(ds.getAcquisitionDetails())), - - new KeyValueItemExportable() - imageTypeValue.setText(""); - sizeValue.setText(""); - sectorSizeValue.setText(""); - md5HashValue.setText(""); - sha1HashValue.setText(""); - sha256HashValue.setText(""); - unallocatedSizeValue.setText(""); - ((DefaultTableModel) filePathsTable.getModel()).setRowCount(0); - )) - ); + new KeyValueItemExportable(Bundle.ContainerPanel_displayNameLabel_text(), result.getDisplayName()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), result.getOriginalName()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), result.getDeviceId()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), timeZone), + new TitledExportable(Bundle.ContainerPanel_export_acquisitionDetails(), getAcquisitionDetails(result.getAcquisitionDetails())), + new KeyValueItemExportable(Bundle.ContainerPanel_export_imageType(), imageType), + new KeyValueItemExportable(Bundle.ContainerPanel_export_size(), size), + new KeyValueItemExportable(Bundle.ContainerPanel_export_sectorSize(), sectorSize), + new KeyValueItemExportable(Bundle.ContainerPanel_export_md5(), md5), + new KeyValueItemExportable(Bundle.ContainerPanel_export_sha1(), sha1), + new KeyValueItemExportable(Bundle.ContainerPanel_export_sha256(), sha256), + new KeyValueItemExportable(Bundle.ContainerPanel_export_unallocatedSize(), unallocatedSize), + new TitledExportable(Bundle.ContainerPanel_export_filePaths(), cellPaths) + ))); + } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java index 34c622563d..b49e8db2ec 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java @@ -32,14 +32,45 @@ public final class SizeRepresentationUtil { private static final int SIZE_CONVERSION_CONSTANT = 1000; private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##"); - private static List UNITS = Arrays.asList( - Bundle.SizeRepresentationUtil_units_bytes(), - Bundle.SizeRepresentationUtil_units_kilobytes(), - Bundle.SizeRepresentationUtil_units_megabytes(), - Bundle.SizeRepresentationUtil_units_gigabytes(), - Bundle.SizeRepresentationUtil_units_terabytes(), - Bundle.SizeRepresentationUtil_units_petabytes() - ); + // based on https://www.mrexcel.com/board/threads/how-do-i-format-cells-to-show-gb-mb-kb.140135/ + @NbBundle.Messages({ + "SizeRepresentationUtil_units_bytes=bytes", + "SizeRepresentationUtil_units_kilobytes=KB", + "SizeRepresentationUtil_units_megabytes=MB", + "SizeRepresentationUtil_units_gigabytes=GB", + "SizeRepresentationUtil_units_terabytes=TB", + "SizeRepresentationUtil_units_petabytes=PB" + }) + public enum SizeUnit { + B(Bundle.SizeRepresentationUtil_units_bytes(), "#", 0), + KB(Bundle.SizeRepresentationUtil_units_kilobytes(), "#,##0.0,", 1), + MB(Bundle.SizeRepresentationUtil_units_megabytes(), "#,##0.0,,", 2), + GB(Bundle.SizeRepresentationUtil_units_gigabytes(), "#,##0.0,,,", 3), + TB(Bundle.SizeRepresentationUtil_units_terabytes(), "#,##0.0,,,,", 4), + PB(Bundle.SizeRepresentationUtil_units_petabytes(), "#,##0.0,,,,,", 5); + + private final String suffix; + private final String excelFormatString; + private final long divisor; + + SizeUnit(String suffix, String excelFormatString, int power) { + this.suffix = suffix; + this.excelFormatString = String.format("%s \"%s\"", excelFormatString, suffix); + this.divisor = (long) Math.pow(SIZE_CONVERSION_CONSTANT, power); + } + + public String getSuffix() { + return suffix; + } + + public String getExcelFormatString() { + return excelFormatString; + } + + public long getDivisor() { + return divisor; + } + } /** * Get a long size in bytes as a string formated to be read by users. @@ -47,50 +78,48 @@ public final class SizeRepresentationUtil { * @param size Long value representing a size in bytes. * * @return Return a string formated with a user friendly version of the size - * as a string, returns empty String when provided empty size. + * as a string, returns empty String when provided empty size. */ public static String getSizeString(Long size) { return getSizeString(size, APPROXIMATE_SIZE_FORMAT, true); } + public static SizeUnit getSizeUnit(Long size) { + if (size == null) { + return SizeUnit.values()[0]; + } + + for (int unitsIndex = 0; unitsIndex < SizeUnit.values().length; unitsIndex++) { + SizeUnit unit = SizeUnit.values()[unitsIndex]; + long result = size / unit.getDivisor(); + if (result < SIZE_CONVERSION_CONSTANT) { + return unit; + } + } + + return SizeUnit.values()[SizeUnit.values().length - 1]; + } + /** * Get a long size in bytes as a string formated to be read by users. * - * @param size Long value representing a size in byte.s - * @param format The means of formatting the number. + * @param size Long value representing a size in byte.s + * @param format The means of formatting the number. * @param showFullSize Optionally show the number of bytes in the - * datasource. + * datasource. * * @return Return a string formated with a user friendly version of the size - * as a string, returns empty String when provided empty size. + * as a string, returns empty String when provided empty size. */ - @NbBundle.Messages({ - "SizeRepresentationUtil_units_bytes= bytes", - "SizeRepresentationUtil_units_kilobytes= kB", - "SizeRepresentationUtil_units_megabytes= MB", - "SizeRepresentationUtil_units_gigabytes= GB", - "SizeRepresentationUtil_units_terabytes= TB", - "SizeRepresentationUtil_units_petabytes= PB" - }) public static String getSizeString(Long size, DecimalFormat format, boolean showFullSize) { if (size == null) { return ""; } - double approximateSize = size; - int unitsIndex = 0; - for (; unitsIndex < UNITS.size(); unitsIndex++) { - if (approximateSize < SIZE_CONVERSION_CONSTANT) { - break; - } else { - approximateSize /= SIZE_CONVERSION_CONSTANT; - } - } - String fullSize = size + UNITS.get(0); - String closestUnitSize = format.format(approximateSize) + UNITS.get(unitsIndex); - - if (unitsIndex == 0) { - return fullSize; + SizeUnit sizeUnit = getSizeUnit(size); + + if (sizeUnit == null || sizeUnit.equals(SizeUnit.B)) { + return String.format("%d %s", size, SizeUnit.B.getSuffix()); } else if (showFullSize) { return String.format("%s (%s)", closestUnitSize, fullSize); } else { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java index f8c1a7a082..f26a2f4f97 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -55,20 +55,38 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; } + public static class SingleCellExportable implements ExcelItemExportable { + + private final ExcelCellModel item; + + public SingleCellExportable(String key) { + this(new DefaultCellModel<>(key)); + } + + public SingleCellExportable(ExcelCellModel item) { + this.item = item; + } + + @Override + public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + Row row = sheet.getRow(rowStart); + ExcelExport.createCell(row, colStart, item, Optional.empty()); + return rowStart; + } + } + public static class KeyValueItemExportable implements ExcelItemExportable { private final ExcelCellModel key; private final ExcelCellModel value; - private final int colStart; - public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) { - this(key, value, 1); + public KeyValueItemExportable(String key, ExcelCellModel value) { + this(new DefaultCellModel<>(key), value); } - public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value, int colStart) { + public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) { this.key = key; this.value = value; - this.colStart = colStart; } @Override @@ -112,7 +130,7 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { int endRow = export.write(sheet, rowStart, colStart + DEFAULT_INDENT, env); curRow = endRow + 1; } - + return curRow; } From 3df61c0d7e3da441f571b926ed23ce8f59ca7c3c Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 15 Mar 2021 20:58:03 -0400 Subject: [PATCH 06/14] fix for default cell model --- .../autopsy/datasourcesummary/uiutils/DefaultCellModel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java index a409794387..009405bf4b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java @@ -76,6 +76,7 @@ public class DefaultCellModel implements GuiCellModel, ExcelCellModel { this.data = data; this.stringConverter = stringConverter; this.excelFormatString = excelFormatString; + this.tooltip = getText(); } @Override From f2cfcde507319bbdb490c623fadf26babe7743f1 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Mon, 15 Mar 2021 22:10:42 -0400 Subject: [PATCH 07/14] working draft of container panel --- .../ui/Bundle.properties-MERGED | 26 +++++++-- .../datasourcesummary/ui/ContainerPanel.java | 27 +++++++-- .../ui/SizeRepresentationUtil.java | 23 +++++--- .../uiutils/ExcelSpecialFormatExport.java | 55 ++++++++++++------- .../uiutils/ExcelTableExport.java | 31 ++++++----- 5 files changed, 109 insertions(+), 53 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 1e4ce97e7d..48bfb7c60b 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -6,7 +6,21 @@ AnalysisPanel_keywordHits_tabName=Keyword Hits AnalysisPanel_keywordSearchModuleName=Keyword Search BaseDataSourceSummaryPanel_goToArtifact=View Source Result BaseDataSourceSummaryPanel_goToFile=View Source File in Directory +ContainerPanel_export_acquisitionDetails=Acquisition Details: +ContainerPanel_export_deviceId=Device ID: +ContainerPanel_export_displayName=Display Name: +ContainerPanel_export_filePaths=File Paths: +ContainerPanel_export_imageType=Image Type: +ContainerPanel_export_md5=MD5: +ContainerPanel_export_originalName=Name: +ContainerPanel_export_sectorSize=Sector Size: +ContainerPanel_export_sha1=SHA1: +ContainerPanel_export_sha256=SHA256: +ContainerPanel_export_size=Size: +ContainerPanel_export_timeZone=Time Zone: +ContainerPanel_export_unallocatedSize=Unallocated Space: ContainerPanel_setFieldsForNonImageDataSource_na=N/A +ContainerPanel_tabName=Container CTL_DataSourceSummaryAction=Data Source Summary DataSourceSummaryDialog.closeButton.text=Close ContainerPanel.displayNameLabel.text=Display Name: @@ -87,12 +101,12 @@ RecentFilesPanel_attachmentsTable_tabName=Recent Attachments RecentFilesPanel_col_head_date=Date RecentFilesPanel_docsTable_tabName=Recently Opened Documents RecentFilesPanel_downloadsTable_tabName=Recently Downloads -SizeRepresentationUtil_units_bytes=\ bytes -SizeRepresentationUtil_units_gigabytes=\ GB -SizeRepresentationUtil_units_kilobytes=\ kB -SizeRepresentationUtil_units_megabytes=\ MB -SizeRepresentationUtil_units_petabytes=\ PB -SizeRepresentationUtil_units_terabytes=\ TB +SizeRepresentationUtil_units_bytes=bytes +SizeRepresentationUtil_units_gigabytes=GB +SizeRepresentationUtil_units_kilobytes=KB +SizeRepresentationUtil_units_megabytes=MB +SizeRepresentationUtil_units_petabytes=PB +SizeRepresentationUtil_units_terabytes=TB TimelinePanel_earliestLabel_title=Earliest TimelinePanel_latestLabel_title=Latest TimlinePanel_last30DaysChart_artifactEvts_title=Result Events diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index 02432f9e0e..8813262db1 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -389,11 +389,30 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { return new DefaultCellModel<>(""); } else { SizeUnit unit = SizeRepresentationUtil.getSizeUnit(bytes); - return new DefaultCellModel<>(bytes, unit.getExcelFormatString()); + if (unit == null) { + unit = SizeUnit.BYTES; + } + + return new DefaultCellModel(bytes, SizeRepresentationUtil::getSizeString, unit.getExcelFormatString()); } } + @Override + @Messages({ + "ContainerPanel_export_displayName=Display Name:", + "ContainerPanel_export_originalName=Name:", + "ContainerPanel_export_deviceId=Device ID:", + "ContainerPanel_export_timeZone=Time Zone:", + "ContainerPanel_export_acquisitionDetails=Acquisition Details:", + "ContainerPanel_export_imageType=Image Type:", + "ContainerPanel_export_size=Size:", + "ContainerPanel_export_sectorSize=Sector Size:", + "ContainerPanel_export_md5=MD5:", + "ContainerPanel_export_sha1=SHA1:", + "ContainerPanel_export_sha256=SHA256:", + "ContainerPanel_export_unallocatedSize=Unallocated Space:", + "ContainerPanel_export_filePaths=File Paths:",}) protected List getExports(DataSource ds) { ContainerViewModel result = getFetchResult(containerDataFetcher, "Container sheets", ds); if (ds == null || result == null) { @@ -421,9 +440,9 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { return Arrays.asList( new ExcelSpecialFormatExport(Bundle.ContainerPanel_tabName(), Arrays.asList( - new KeyValueItemExportable(Bundle.ContainerPanel_displayNameLabel_text(), result.getDisplayName()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), result.getOriginalName()), - new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), result.getDeviceId()), + new KeyValueItemExportable(Bundle.ContainerPanel_export_displayName(), new DefaultCellModel<>(result.getDisplayName())), + new KeyValueItemExportable(Bundle.ContainerPanel_export_originalName(), new DefaultCellModel<>(result.getOriginalName())), + new KeyValueItemExportable(Bundle.ContainerPanel_export_deviceId(), new DefaultCellModel<>(result.getDeviceId())), new KeyValueItemExportable(Bundle.ContainerPanel_export_timeZone(), timeZone), new TitledExportable(Bundle.ContainerPanel_export_acquisitionDetails(), getAcquisitionDetails(result.getAcquisitionDetails())), new KeyValueItemExportable(Bundle.ContainerPanel_export_imageType(), imageType), diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java index b49e8db2ec..a6d0229637 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java @@ -42,12 +42,12 @@ public final class SizeRepresentationUtil { "SizeRepresentationUtil_units_petabytes=PB" }) public enum SizeUnit { - B(Bundle.SizeRepresentationUtil_units_bytes(), "#", 0), - KB(Bundle.SizeRepresentationUtil_units_kilobytes(), "#,##0.0,", 1), - MB(Bundle.SizeRepresentationUtil_units_megabytes(), "#,##0.0,,", 2), - GB(Bundle.SizeRepresentationUtil_units_gigabytes(), "#,##0.0,,,", 3), - TB(Bundle.SizeRepresentationUtil_units_terabytes(), "#,##0.0,,,,", 4), - PB(Bundle.SizeRepresentationUtil_units_petabytes(), "#,##0.0,,,,,", 5); + BYTES(Bundle.SizeRepresentationUtil_units_bytes(), "#", 0), + KB(Bundle.SizeRepresentationUtil_units_kilobytes(), "#,##0.00,", 1), + MB(Bundle.SizeRepresentationUtil_units_megabytes(), "#,##0.00,,", 2), + GB(Bundle.SizeRepresentationUtil_units_gigabytes(), "#,##0.00,,,", 3), + TB(Bundle.SizeRepresentationUtil_units_terabytes(), "#,##0.00,,,,", 4), + PB(Bundle.SizeRepresentationUtil_units_petabytes(), "#,##0.00,,,,,", 5); private final String suffix; private final String excelFormatString; @@ -117,9 +117,16 @@ public final class SizeRepresentationUtil { } SizeUnit sizeUnit = getSizeUnit(size); + if (sizeUnit == null) { + sizeUnit = SizeUnit.BYTES; + } - if (sizeUnit == null || sizeUnit.equals(SizeUnit.B)) { - return String.format("%d %s", size, SizeUnit.B.getSuffix()); + String closestUnitSize = String.format("%s %s", + format.format(((double) size) / sizeUnit.getDivisor()), sizeUnit.getSuffix()); + + String fullSize = String.format("%d %s", size, SizeUnit.BYTES.getSuffix()); + if (sizeUnit.equals(SizeUnit.BYTES)) { + return fullSize; } else if (showFullSize) { return String.format("%s (%s)", closestUnitSize, fullSize); } else { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java index f26a2f4f97..d738452ba0 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -19,17 +19,17 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCel */ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { - public static class ExcelItemResult { + public static class ItemDimensions { private final int rowStart; private final int rowEnd; private final int colStart; private final int colEnd; - public ExcelItemResult(int rowStart, int rowEnd, int colStart, int colEnd) { + public ItemDimensions(int rowStart, int colStart, int rowEnd, int colEnd) { this.rowStart = rowStart; - this.rowEnd = rowEnd; this.colStart = colStart; + this.rowEnd = rowEnd; this.colEnd = colEnd; } @@ -52,7 +52,7 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { public interface ExcelItemExportable { - int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; + ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; } public static class SingleCellExportable implements ExcelItemExportable { @@ -68,10 +68,13 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { } @Override - public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { - Row row = sheet.getRow(rowStart); - ExcelExport.createCell(row, colStart, item, Optional.empty()); - return rowStart; + public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + Row row = sheet.createRow(rowStart); + ExcelExport.createCell(row, colStart, item, + item.getExcelFormatString() == null + ? Optional.empty() + : Optional.of(ExcelExport.createCellStyle(env.getParentWorkbook(), item.getExcelFormatString()))); + return new ItemDimensions(rowStart, colStart, rowStart, colStart); } } @@ -90,11 +93,14 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { } @Override - public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { - Row row = sheet.getRow(rowStart); + public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + Row row = sheet.createRow(rowStart); ExcelExport.createCell(row, colStart, key, Optional.of(env.getHeaderStyle())); - ExcelExport.createCell(row, colStart + 1, value, Optional.empty()); - return rowStart + 1; + ExcelExport.createCell(row, colStart + 1, value, + value.getExcelFormatString() == null + ? Optional.empty() + : Optional.of(ExcelExport.createCellStyle(env.getParentWorkbook(), value.getExcelFormatString()))); + return new ItemDimensions(rowStart, colStart, rowStart, colStart + 1); } } @@ -119,19 +125,21 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { } @Override - public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { - ExcelExport.createCell(sheet.getRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle())); + public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + ExcelExport.createCell(sheet.createRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle())); int curRow = rowStart + 1; + int maxCol = colStart; for (ExcelItemExportable export : children) { if (export == null) { continue; } - int endRow = export.write(sheet, rowStart, colStart + DEFAULT_INDENT, env); - curRow = endRow + 1; + ItemDimensions thisItemDim = export.write(sheet, curRow, colStart + DEFAULT_INDENT, env); + curRow = thisItemDim.getRowEnd() + 1; + maxCol = Math.max(thisItemDim.getColEnd(), maxCol); } - return curRow; + return new ItemDimensions(rowStart, colStart, curRow - 1, maxCol); } } @@ -143,14 +151,21 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { @Override public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv env) throws ExcelExportException { - int rowStart = 1; + int rowStart = 0; + int maxCol = 0; for (ExcelItemExportable export : exports) { if (export == null) { continue; } - int endRow = export.write(sheet, rowStart, 1, env); - rowStart = endRow + 1; + ItemDimensions dimensions = export.write(sheet, rowStart, 0, env); + rowStart = dimensions.getRowEnd() + 1; + maxCol = Math.max(maxCol, dimensions.getColEnd()); + } + + // Resize all columns to fit the content size + for (int i = 0; i <= maxCol; i++) { + sheet.autoSizeColumn(i); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index 6f108af9ae..e4c75c73d7 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -30,6 +30,7 @@ import org.apache.poi.ss.usermodel.Sheet; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ItemDimensions; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; /** @@ -74,14 +75,19 @@ public class ExcelTableExport implements ExcelSheet @Override public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException { - renderSheet(sheet, style, 1, 1, columns, data); + renderSheet(sheet, style, 0, 0, columns, data); + + // Resize all columns to fit the content size + for (int i = 0; i < columns.size(); i++) { + sheet.autoSizeColumn(i); + } } @Override - public int write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { + public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { int rowsWritten = renderSheet(sheet, env, rowStart, colStart, columns, data); - return rowStart + rowsWritten - 1; + return new ItemDimensions(rowStart, colStart, rowStart + rowsWritten - 1, this.columns == null ? colStart : colStart + this.columns.size()); } /** @@ -97,19 +103,19 @@ public class ExcelTableExport implements ExcelSheet * @return The number of rows (including the header) written. */ private static int renderSheet( - Sheet sheet, - ExcelExport.WorksheetEnv worksheetEnv, - int rowStart, + Sheet sheet, + ExcelExport.WorksheetEnv worksheetEnv, + int rowStart, int colStart, List> columns, List data) throws ExcelExport.ExcelExportException { List safeData = data == null ? Collections.emptyList() : data; // Create a header row - Row headerRow = sheet.createRow(0); + Row headerRow = sheet.createRow(rowStart); // Create header cells for (int i = 0; i < columns.size(); i++) { - Cell cell = headerRow.createCell(i); + Cell cell = headerRow.createCell(i + colStart); cell.setCellValue(columns.get(i).getHeaderTitle()); cell.setCellStyle(worksheetEnv.getHeaderStyle()); } @@ -119,7 +125,7 @@ public class ExcelTableExport implements ExcelSheet Map cellStyles = new HashMap<>(); for (int rowNum = 0; rowNum < safeData.size(); rowNum++) { T rowData = safeData.get(rowNum); - Row row = sheet.createRow(rowNum + 1); + Row row = sheet.createRow(rowNum + rowStart + 1); for (int colNum = 0; colNum < columns.size(); colNum++) { ColumnModel colModel = columns.get(colNum); ExcelCellModel cellModel = colModel.getCellRenderer().apply(rowData); @@ -130,12 +136,7 @@ public class ExcelTableExport implements ExcelSheet ExcelExport.createCell(row, colNum + colStart, cellModel, cellStyle); } } - - // Resize all columns to fit the content size - for (int i = colStart; i < columns.size() + colStart; i++) { - sheet.autoSizeColumn(i); - } - + return safeData.size() + 1; } } From 7f9f935b2aae17d0361534f7dca988d5a9a64980 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 16 Mar 2021 09:57:49 -0400 Subject: [PATCH 08/14] working through remaining tabs --- .../ui/Bundle.properties-MERGED | 1 + .../datasourcesummary/ui/ContainerPanel.java | 20 +--- .../ui/DataSourceSummaryTabbedPane.java | 2 +- .../ui/SizeRepresentationUtil.java | 23 +++- .../datasourcesummary/ui/TypesPanel.java | 107 ++++++++++++------ .../uiutils/ExcelTableExport.java | 13 ++- 6 files changed, 104 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 48bfb7c60b..04dc52f280 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -113,6 +113,7 @@ TimlinePanel_last30DaysChart_artifactEvts_title=Result Events TimlinePanel_last30DaysChart_fileEvts_title=File Events TimlinePanel_last30DaysChart_title=Last 30 Days TypesPanel_artifactsTypesPieChart_title=Artifact Types +TypesPanel_excelTabName=Types TypesPanel_fileMimeTypesChart_audio_title=Audio TypesPanel_fileMimeTypesChart_documents_title=Documents TypesPanel_fileMimeTypesChart_executables_title=Executables diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index 8813262db1..b74c083e61 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -383,20 +383,6 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { .collect(Collectors.toList()); } } - - private DefaultCellModel getBytesCell(Long bytes) { - if (bytes == null) { - return new DefaultCellModel<>(""); - } else { - SizeUnit unit = SizeRepresentationUtil.getSizeUnit(bytes); - if (unit == null) { - unit = SizeUnit.BYTES; - } - - return new DefaultCellModel(bytes, SizeRepresentationUtil::getSizeString, unit.getExcelFormatString()); - } - } - @Override @Messages({ @@ -427,12 +413,12 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { DefaultCellModel timeZone = hasImage ? new DefaultCellModel<>(imageModel.getTimeZone()) : NACell; DefaultCellModel imageType = hasImage ? new DefaultCellModel<>(imageModel.getImageType()) : NACell; - DefaultCellModel size = hasImage ? getBytesCell(imageModel.getSize()) : NACell; - DefaultCellModel sectorSize = hasImage ? getBytesCell(imageModel.getSectorSize()) : NACell; + DefaultCellModel size = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getSize()) : NACell; + DefaultCellModel sectorSize = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getSectorSize()) : NACell; DefaultCellModel md5 = hasImage ? new DefaultCellModel<>(imageModel.getMd5Hash()) : NACell; DefaultCellModel sha1 = hasImage ? new DefaultCellModel<>(imageModel.getSha1Hash()) : NACell; DefaultCellModel sha256 = hasImage ? new DefaultCellModel<>(imageModel.getSha256Hash()) : NACell; - DefaultCellModel unallocatedSize = hasImage ? getBytesCell(imageModel.getUnallocatedSize()) : NACell; + DefaultCellModel unallocatedSize = hasImage ? SizeRepresentationUtil.getBytesCell(imageModel.getUnallocatedSize()) : NACell; List paths = result.getImageViewModel() == null ? Collections.singletonList(NA) : result.getImageViewModel().getPaths(); List cellPaths = paths.stream() .map(SingleCellExportable::new) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java index 102a75f89b..c65e5e39cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java @@ -155,7 +155,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel, ingestHistoryPanel::setDataSource, - null, + IngestJobExcelExport::getExports, null), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), new ContainerPanel()), new DataSourceTab( diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java index a6d0229637..ef30e790bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java @@ -22,6 +22,7 @@ import java.text.DecimalFormat; import java.util.Arrays; import java.util.List; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; /** * This class provides utilities for representing storage size in most relevant @@ -41,7 +42,7 @@ public final class SizeRepresentationUtil { "SizeRepresentationUtil_units_terabytes=TB", "SizeRepresentationUtil_units_petabytes=PB" }) - public enum SizeUnit { + enum SizeUnit { BYTES(Bundle.SizeRepresentationUtil_units_bytes(), "#", 0), KB(Bundle.SizeRepresentationUtil_units_kilobytes(), "#,##0.00,", 1), MB(Bundle.SizeRepresentationUtil_units_megabytes(), "#,##0.00,,", 2), @@ -80,11 +81,11 @@ public final class SizeRepresentationUtil { * @return Return a string formated with a user friendly version of the size * as a string, returns empty String when provided empty size. */ - public static String getSizeString(Long size) { + static String getSizeString(Long size) { return getSizeString(size, APPROXIMATE_SIZE_FORMAT, true); } - public static SizeUnit getSizeUnit(Long size) { + static SizeUnit getSizeUnit(Long size) { if (size == null) { return SizeUnit.values()[0]; } @@ -111,7 +112,7 @@ public final class SizeRepresentationUtil { * @return Return a string formated with a user friendly version of the size * as a string, returns empty String when provided empty size. */ - public static String getSizeString(Long size, DecimalFormat format, boolean showFullSize) { + static String getSizeString(Long size, DecimalFormat format, boolean showFullSize) { if (size == null) { return ""; } @@ -133,6 +134,20 @@ public final class SizeRepresentationUtil { return closestUnitSize; } } + + + static DefaultCellModel getBytesCell(Long bytes) { + if (bytes == null) { + return new DefaultCellModel<>(""); + } else { + SizeUnit unit = SizeRepresentationUtil.getSizeUnit(bytes); + if (unit == null) { + unit = SizeUnit.BYTES; + } + + return new DefaultCellModel(bytes, SizeRepresentationUtil::getSizeString, unit.getExcelFormatString()); + } + } private SizeRepresentationUtil() { } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java index ae5598cb92..b63150814a 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java @@ -40,7 +40,11 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.IngestRunningLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; @@ -72,7 +76,8 @@ import org.sleuthkit.datamodel.TskCoreException; "TypesPanel_fileMimeTypesChart_notAnalyzed_title=Not Analyzed", "TypesPanel_usageLabel_title=Usage", "TypesPanel_osLabel_title=OS", - "TypesPanel_sizeLabel_title=Size"}) + "TypesPanel_sizeLabel_title=Size", + "TypesPanel_excelTabName=Types"}) class TypesPanel extends BaseDataSourceSummaryPanel { /** @@ -167,9 +172,9 @@ class TypesPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; private static final DecimalFormat INTEGER_SIZE_FORMAT = new DecimalFormat("#"); - private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat("#,###"); - private static final String FILE_TYPE_FACTORY = FileTypeIdModuleFactory.class.getCanonicalName(); - private static final String FILE_TYPE_MODULE_NAME = FileTypeIdModuleFactory.getModuleName(); + private static final String COMMA_FORMAT_STR = "#,###"; + + private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat(COMMA_FORMAT_STR); private static final Logger logger = Logger.getLogger(TypesPanel.class.getName()); private static final Color IMAGES_COLOR = new Color(156, 39, 176); @@ -191,6 +196,15 @@ class TypesPanel extends BaseDataSourceSummaryPanel { new TypesPieCategory(Bundle.TypesPanel_fileMimeTypesChart_unknown_title(), new HashSet<>(Arrays.asList("application/octet-stream")), UNKNOWN_COLOR) ); + private final DataFetcher usageFetcher; + private final DataFetcher osFetcher; + private final DataFetcher sizeFetcher; + + private final DataFetcher allocatedFetcher; + private final DataFetcher unallocatedFetcher; + private final DataFetcher slackFetcher; + private final DataFetcher directoriesFetcher; + private final LoadableLabel usageLabel = new LoadableLabel(Bundle.TypesPanel_usageLabel_title()); private final LoadableLabel osLabel = new LoadableLabel(Bundle.TypesPanel_osLabel_title()); private final LoadableLabel sizeLabel = new LoadableLabel(Bundle.TypesPanel_sizeLabel_title()); @@ -246,42 +260,34 @@ class TypesPanel extends BaseDataSourceSummaryPanel { super(mimeTypeData, typeData, containerData); + this.usageFetcher = containerData::getDataSourceType; + this.osFetcher = containerData::getOperatingSystems; + + this.sizeFetcher = (dataSource) -> dataSource == null ? null : dataSource.getSize(); + + this.allocatedFetcher = (dataSource) -> typeData.getCountOfAllocatedFiles(dataSource); + this.unallocatedFetcher = (dataSource) -> typeData.getCountOfUnallocatedFiles(dataSource); + this.slackFetcher = (dataSource) -> typeData.getCountOfSlackFiles(dataSource); + this.directoriesFetcher = (dataSource) -> typeData.getCountOfDirectories(dataSource); + this.dataFetchComponents = Arrays.asList( - // usage label worker - new DataFetchWorker.DataFetchComponents<>( - containerData::getDataSourceType, - (result) -> usageLabel.showDataFetchResult(result)), - // os label worker - new DataFetchWorker.DataFetchComponents<>( - containerData::getOperatingSystems, - (result) -> osLabel.showDataFetchResult(result)), - // size label worker - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> { - Long size = dataSource == null ? null : dataSource.getSize(); - return SizeRepresentationUtil.getSizeString(size, INTEGER_SIZE_FORMAT, false); - }, - sizeLabel::showDataFetchResult), - // file types worker + new DataFetchWorker.DataFetchComponents<>(usageFetcher, usageLabel::showDataFetchResult), + new DataFetchWorker.DataFetchComponents<>(osFetcher, osLabel::showDataFetchResult), + new DataFetchWorker.DataFetchComponents<>(sizeFetcher, + (sizeResult) -> sizeLabel.showDataFetchResult( + DataFetchResult.getSubResult(sizeResult, + size -> SizeRepresentationUtil.getSizeString(size, INTEGER_SIZE_FORMAT, false)))), new DataFetchWorker.DataFetchComponents<>( (dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource), this::showMimeTypeCategories), - // allocated files worker - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> getStringOrZero(typeData.getCountOfAllocatedFiles(dataSource)), - allocatedLabel::showDataFetchResult), - // unallocated files worker - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> getStringOrZero(typeData.getCountOfUnallocatedFiles(dataSource)), - unallocatedLabel::showDataFetchResult), - // slack files worker - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> getStringOrZero(typeData.getCountOfSlackFiles(dataSource)), - slackLabel::showDataFetchResult), - // directories worker - new DataFetchWorker.DataFetchComponents<>( - (dataSource) -> getStringOrZero(typeData.getCountOfDirectories(dataSource)), - directoriesLabel::showDataFetchResult) + new DataFetchWorker.DataFetchComponents<>(allocatedFetcher, + countRes -> allocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + new DataFetchWorker.DataFetchComponents<>(unallocatedFetcher, + countRes -> unallocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + new DataFetchWorker.DataFetchComponents<>(slackFetcher, + countRes -> slackLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + new DataFetchWorker.DataFetchComponents<>(directoriesFetcher, + countRes -> directoriesLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))) ); initComponents(); @@ -406,9 +412,36 @@ class TypesPanel extends BaseDataSourceSummaryPanel { return longVal == null ? "0" : COMMA_FORMATTER.format(longVal); } + private static KeyValueItemExportable getStrExportable(DataFetcher fetcher, String key, DataSource dataSource) { + String result = getFetchResult(fetcher, "Types", dataSource); + return (result == null) ? null : new KeyValueItemExportable(key, new DefaultCellModel<>(result)); + } + + private static KeyValueItemExportable getCountExportable(DataFetcher fetcher, String key, DataSource dataSource) { + Long count = getFetchResult(fetcher, "Types", dataSource); + return (count == null) ? null : new KeyValueItemExportable(key, + new DefaultCellModel(count, COMMA_FORMATTER::format, COMMA_FORMAT_STR)); + } + @Override List getExports(DataSource dataSource) { - return Collections.emptyList(); + if (dataSource == null) { + return Collections.emptyList(); + } + + return Arrays.asList(new ExcelSpecialFormatExport(Bundle.TypesPanel_excelTabName(), + Stream.of( + getStrExportable(usageFetcher, Bundle.TypesPanel_usageLabel_title(), dataSource), + getStrExportable(osFetcher, Bundle.TypesPanel_osLabel_title(), dataSource), + new KeyValueItemExportable(Bundle.TypesPanel_sizeLabel_title(), + SizeRepresentationUtil.getBytesCell(getFetchResult(sizeFetcher, "Types", dataSource))), + getCountExportable(allocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_allocatedRow_title(), dataSource), + getCountExportable(unallocatedFetcher, Bundle.TypesPanel_filesByCategoryTable_unallocatedRow_title(), dataSource), + getCountExportable(slackFetcher, Bundle.TypesPanel_filesByCategoryTable_slackRow_title(), dataSource), + getCountExportable(directoriesFetcher, Bundle.TypesPanel_filesByCategoryTable_directoryRow_title(), dataSource)) + .filter(sheet -> sheet != null) + .collect(Collectors.toList()) + )); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index e4c75c73d7..887ed6f323 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -53,6 +53,7 @@ public class ExcelTableExport implements ExcelSheet private final String sheetName; private final List> columns; private final List data; + private final int columnIndent; /** * Main constructor. @@ -63,9 +64,14 @@ public class ExcelTableExport implements ExcelSheet * @param data The data to export. */ public ExcelTableExport(String sheetName, List> columns, List data) { + this(sheetName, columns, data, 0); + } + + public ExcelTableExport(String sheetName, List> columns, List data, int columnIndent) { this.sheetName = sheetName; this.columns = columns; this.data = data; + this.columnIndent = columnIndent; } @Override @@ -75,7 +81,7 @@ public class ExcelTableExport implements ExcelSheet @Override public void renderSheet(Sheet sheet, ExcelExport.WorksheetEnv style) throws ExcelExport.ExcelExportException { - renderSheet(sheet, style, 0, 0, columns, data); + renderSheet(sheet, style, 0, columnIndent, columns, data); // Resize all columns to fit the content size for (int i = 0; i < columns.size(); i++) { @@ -86,8 +92,9 @@ public class ExcelTableExport implements ExcelSheet @Override public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { - int rowsWritten = renderSheet(sheet, env, rowStart, colStart, columns, data); - return new ItemDimensions(rowStart, colStart, rowStart + rowsWritten - 1, this.columns == null ? colStart : colStart + this.columns.size()); + int columnStart = columnIndent + colStart; + int rowsWritten = renderSheet(sheet, env, rowStart, columnStart, columns, data); + return new ItemDimensions(rowStart, columnStart, rowStart + rowsWritten - 1, this.columns == null ? columnStart : columnStart + this.columns.size()); } /** From 62b2628064fec840cfc5e4e758b790fe90d8ecb9 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 16 Mar 2021 09:57:56 -0400 Subject: [PATCH 09/14] working through remaining tabs --- .../ui/IngestJobExcelExport.java | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java new file mode 100644 index 0000000000..62cb484e54 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java @@ -0,0 +1,178 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.datasourcesummary.ui; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ColumnModel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.IngestModuleInfo; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * @author gregd + */ +@Messages({ + "IngestJobExcelExport_startTimeColumn=Start Time", + "IngestJobExcelExport_endTimeColumn=End Time", + "IngestJobExcelExport_ingestStatusTimeColumn=Ingest Status", + "IngestJobExcelExport_moduleNameTimeColumn=Module Name", + "IngestJobExcelExport_versionColumn=Module Version", + "IngestJobExcelExport_sheetName=Ingest History" +}) +class IngestJobExcelExport { + + private static class IngestJobEntry { + + private final Date startTime; + private final Date endTime; + private final String status; + private final String ingestModule; + private final String ingestModuleVersion; + + IngestJobEntry(Date startTime, Date endTime, String status, String ingestModule, String ingestModuleVersion) { + this.startTime = startTime; + this.endTime = endTime; + this.status = status; + this.ingestModule = ingestModule; + this.ingestModuleVersion = ingestModuleVersion; + } + + Date getStartTime() { + return startTime; + } + + Date getEndTime() { + return endTime; + } + + String getStatus() { + return status; + } + + String getIngestModule() { + return ingestModule; + } + + String getIngestModuleVersion() { + return ingestModuleVersion; + } + } + + private static final Logger logger = Logger.getLogger(IngestJobExcelExport.class.getName()); + private static final String DATETIME_FORMAT_STR = "yyyy/MM/dd HH:mm:ss"; + private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_STR, Locale.getDefault()); + + private static final List>> COLUMNS = Arrays.asList( + new ColumnModel<>( + Bundle.IngestJobExcelExport_startTimeColumn(), + (entry) -> getDateCell(entry.getStartTime())), + new ColumnModel<>( + Bundle.IngestJobExcelExport_endTimeColumn(), + (entry) -> getDateCell(entry.getEndTime())), + new ColumnModel<>( + Bundle.IngestJobExcelExport_ingestStatusTimeColumn(), + (entry) -> new DefaultCellModel<>(entry.getStatus())), + new ColumnModel<>( + Bundle.IngestJobExcelExport_moduleNameTimeColumn(), + (entry) -> new DefaultCellModel<>(entry.getIngestModule())), + new ColumnModel<>( + Bundle.IngestJobExcelExport_versionColumn(), + (entry) -> new DefaultCellModel<>(entry.getIngestModuleVersion())) + ); + + private static DefaultCellModel getDateCell(Date date) { + Function dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt); + return new DefaultCellModel<>(date, dateParser, DATETIME_FORMAT_STR); + } + + private static List getEntries(IngestJobInfo job) { + List infoList = job.getIngestModuleInfo(); + if (infoList == null) { + return Collections.emptyList(); + } else { + Date startTime = job.getStartDateTime(); + Date endTime = job.getEndDateTime(); + String status = job.getStatus().getDisplayName(); + + return infoList.stream() + .filter(info -> info != null) + .map(info -> new IngestJobEntry(startTime, endTime, status, info.getDisplayName(), info.getVersion())) + .sorted((a,b) -> StringUtils.compareIgnoreCase(a.getIngestModule(), b.getIngestModule())) + .collect(Collectors.toList()); + } + } + + private static Stream showFirstRowOnly(List list) { + return IntStream.range(0, list.size()) + .mapToObj(idx -> { + IngestJobEntry entry = list.get(idx); + if (entry == null || idx == 0) { + return entry; + } else { + return new IngestJobEntry(null, null, null, entry.getIngestModule(), entry.getIngestModuleVersion()); + } + }); + + } + + static List getExports(DataSource dataSource) { + if (dataSource == null) { + return Collections.emptyList(); + } + + List info = null; + try { + info = Case.getCurrentCaseThrows().getSleuthkitCase().getIngestJobs(); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.WARNING, "There was an error fetching ingest jobs", ex); + } + + if (info == null) { + info = Collections.emptyList(); + } + + List toDisplay = info.stream() + .filter(job -> job != null && dataSource.getId() == job.getObjectId()) + .sorted((a,b) -> { + boolean aIsNull = a.getStartDateTime() == null; + boolean bIsNull = b.getStartDateTime() == null; + if (aIsNull || bIsNull) { + return Boolean.compare(bIsNull, aIsNull); + } else { + return a.getStartDateTime().compareTo(b.getStartDateTime()); + } + }) + .map((job) -> getEntries(job)) + .filter(lst -> lst != null) + .flatMap((lst) -> showFirstRowOnly(lst)) + .filter(item -> item != null) + .collect(Collectors.toList()); + + return Arrays.asList(new ExcelTableExport(Bundle.IngestJobExcelExport_sheetName(), COLUMNS, toDisplay)); + } +} From 58a8a2a628315fc7be6d1057f9fc7fe2a9c2d26d Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 16 Mar 2021 10:53:12 -0400 Subject: [PATCH 10/14] bug fixes --- .../ui/Bundle.properties-MERGED | 6 ++++ .../datasourcesummary/ui/ContainerPanel.java | 2 +- .../ui/IngestJobExcelExport.java | 28 ++++++++++++------- .../datasourcesummary/ui/TypesPanel.java | 8 +++--- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED index 04dc52f280..4a47bcb092 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -87,6 +87,12 @@ GeolocationPanel_mostCommon_tabName=Most Common Cities GeolocationPanel_mostRecent_tabName=Most Recent Cities GeolocationPanel_onNoCrIngest_message=No results will be shown because the GPX Parser was not run. GeolocationPanel_unknownRow_title=Unknown +IngestJobExcelExport_endTimeColumn=End Time +IngestJobExcelExport_ingestStatusTimeColumn=Ingest Status +IngestJobExcelExport_moduleNameTimeColumn=Module Name +IngestJobExcelExport_sheetName=Ingest History +IngestJobExcelExport_startTimeColumn=Start Time +IngestJobExcelExport_versionColumn=Module Version PastCasesPanel_caseColumn_title=Case PastCasesPanel_countColumn_title=Count PastCasesPanel_notableFileTable_tabName=Cases with Common Notable diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index b74c083e61..affba43a61 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -337,7 +337,7 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path}); } - md5HashLabel.setText(viewModel.getMd5Hash()); + md5HashValue.setText(viewModel.getMd5Hash()); sha1HashValue.setText(viewModel.getSha1Hash()); sha256HashValue.setText(viewModel.getSha256Hash()); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java index 62cb484e54..51e0407286 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java @@ -105,7 +105,7 @@ class IngestJobExcelExport { (entry) -> new DefaultCellModel<>(entry.getIngestModuleVersion())) ); - private static DefaultCellModel getDateCell(Date date) { + private static DefaultCellModel getDateCell(Date date) { Function dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt); return new DefaultCellModel<>(date, dateParser, DATETIME_FORMAT_STR); } @@ -118,11 +118,19 @@ class IngestJobExcelExport { Date startTime = job.getStartDateTime(); Date endTime = job.getEndDateTime(); String status = job.getStatus().getDisplayName(); - + return infoList.stream() .filter(info -> info != null) .map(info -> new IngestJobEntry(startTime, endTime, status, info.getDisplayName(), info.getVersion())) - .sorted((a,b) -> StringUtils.compareIgnoreCase(a.getIngestModule(), b.getIngestModule())) + .sorted((a, b) -> { + boolean aIsNull = a == null || a.getIngestModule() == null; + boolean bIsNull = b == null || b.getIngestModule() == null; + if (aIsNull || bIsNull) { + return Boolean.compare(aIsNull, bIsNull); + } else { + return a.getIngestModule().compareTo(b.getIngestModule()); + } + }) .collect(Collectors.toList()); } } @@ -137,7 +145,7 @@ class IngestJobExcelExport { return new IngestJobEntry(null, null, null, entry.getIngestModule(), entry.getIngestModuleVersion()); } }); - + } static List getExports(DataSource dataSource) { @@ -151,18 +159,18 @@ class IngestJobExcelExport { } catch (NoCurrentCaseException | TskCoreException ex) { logger.log(Level.WARNING, "There was an error fetching ingest jobs", ex); } - + if (info == null) { info = Collections.emptyList(); } - + List toDisplay = info.stream() .filter(job -> job != null && dataSource.getId() == job.getObjectId()) - .sorted((a,b) -> { + .sorted((a, b) -> { boolean aIsNull = a.getStartDateTime() == null; boolean bIsNull = b.getStartDateTime() == null; if (aIsNull || bIsNull) { - return Boolean.compare(bIsNull, aIsNull); + return Boolean.compare(aIsNull, bIsNull); } else { return a.getStartDateTime().compareTo(b.getStartDateTime()); } @@ -172,7 +180,7 @@ class IngestJobExcelExport { .flatMap((lst) -> showFirstRowOnly(lst)) .filter(item -> item != null) .collect(Collectors.toList()); - - return Arrays.asList(new ExcelTableExport(Bundle.IngestJobExcelExport_sheetName(), COLUMNS, toDisplay)); + + return Arrays.asList(new ExcelTableExport<>(Bundle.IngestJobExcelExport_sheetName(), COLUMNS, toDisplay)); } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java index b63150814a..c7e90e5072 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java @@ -281,13 +281,13 @@ class TypesPanel extends BaseDataSourceSummaryPanel { (dataSource) -> getMimeTypeCategoriesModel(mimeTypeData, dataSource), this::showMimeTypeCategories), new DataFetchWorker.DataFetchComponents<>(allocatedFetcher, - countRes -> allocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + countRes -> allocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))), new DataFetchWorker.DataFetchComponents<>(unallocatedFetcher, - countRes -> unallocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + countRes -> unallocatedLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))), new DataFetchWorker.DataFetchComponents<>(slackFetcher, - countRes -> slackLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))), + countRes -> slackLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))), new DataFetchWorker.DataFetchComponents<>(directoriesFetcher, - countRes -> directoriesLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, this::getStringOrZero))) + countRes -> directoriesLabel.showDataFetchResult(DataFetchResult.getSubResult(countRes, (count) -> getStringOrZero(count)))) ); initComponents(); From 3823ca1403ecf2e09b7b3c98584be38b4f9e180e Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 16 Mar 2021 14:56:14 -0400 Subject: [PATCH 11/14] all left aligned --- .../uiutils/ExcelExport.java | 66 +++++++++++++------ .../uiutils/ExcelSpecialFormatExport.java | 14 ++-- .../uiutils/ExcelTableExport.java | 8 +-- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java index b453fd664d..3e2cbfabe3 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java @@ -23,11 +23,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.Calendar; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import org.apache.commons.lang3.tuple.Pair; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Sheet; @@ -71,17 +75,34 @@ public class ExcelExport { private final CellStyle headerStyle; private final Workbook parentWorkbook; - + private final CellStyle defaultStyle; + + // maps a data format string / original cell style combination to a created cell style + private final Map, CellStyle> cellStyleCache = new HashMap<>(); + /** * Main constructor. * * @param headerStyle The cell style to use for headers. + * @param defaultStyle The cell style to use as a default. * @param parentWorkbook The parent workbook. */ - WorksheetEnv(CellStyle headerStyle, Workbook parentWorkbook) { + WorksheetEnv(CellStyle headerStyle, CellStyle defaultStyle, Workbook parentWorkbook) { this.headerStyle = headerStyle; + this.defaultStyle = defaultStyle; this.parentWorkbook = parentWorkbook; } + + + public CellStyle getCellStyle(CellStyle baseStyle, String dataFormat) { + return cellStyleCache.computeIfAbsent(Pair.of(dataFormat, baseStyle), (pair) -> { + CellStyle computed = this.parentWorkbook.createCellStyle(); + computed.cloneStyleFrom(pair.getRight() == null ? defaultStyle : pair.getRight()); + computed.setDataFormat(this.parentWorkbook.getCreationHelper().createDataFormat().getFormat(dataFormat)); + return computed; + }); + } + /** * Returns the cell style to use for headers. @@ -92,6 +113,15 @@ public class ExcelExport { return headerStyle; } + /** + * Returns the cell style for default items. + * + * @return The cell style for default items. + */ + public CellStyle getDefaultCellStyle() { + return defaultStyle; + } + /** * Returns the parent workbook. * @@ -167,8 +197,12 @@ public class ExcelExport { // Create a CellStyle with the font CellStyle headerCellStyle = workbook.createCellStyle(); headerCellStyle.setFont(headerFont); + headerCellStyle.setAlignment(HorizontalAlignment.LEFT); + + CellStyle defaultCellStyle = workbook.createCellStyle(); + defaultCellStyle.setAlignment(HorizontalAlignment.LEFT); - WorksheetEnv env = new WorksheetEnv(headerCellStyle, workbook); + WorksheetEnv env = new WorksheetEnv(headerCellStyle, defaultCellStyle, workbook); if (exports != null) { for (int i = 0; i < exports.size(); i++) { @@ -195,31 +229,25 @@ public class ExcelExport { // Closing the workbook workbook.close(); } - - - /** - * Create a cell style in the workbook with the given format string. - * - * @param workbook The workbook. - * @param formatString The format string. - * @return The cell style. - */ - static CellStyle createCellStyle(Workbook workbook, String formatString) { - CellStyle cellStyle = workbook.createCellStyle(); - cellStyle.setDataFormat(workbook.getCreationHelper().createDataFormat().getFormat(formatString)); - return cellStyle; - } + /** * Creates an excel cell given the model. * + * @param env The work sheet environment including the workbook. * @param row The row in the excel document. * @param colNum The column number (not zero-indexed). * @param cellModel The model for the cell. * @param cellStyle The style to use. * @return The created cell. */ - static Cell createCell(Row row, int colNum, ExcelTableExport.ExcelCellModel cellModel, Optional cellStyle) { + static Cell createCell(WorksheetEnv env, Row row, int colNum, ExcelTableExport.ExcelCellModel cellModel, Optional cellStyle) { + CellStyle cellStyleToUse = cellStyle.orElse(env.getDefaultCellStyle()); + + if (cellModel.getExcelFormatString() != null) { + cellStyleToUse = env.getCellStyle(cellStyleToUse, cellModel.getExcelFormatString()); + } + Object cellData = cellModel.getData(); Cell cell = row.createCell(colNum); if (cellData instanceof Calendar) { @@ -241,7 +269,7 @@ public class ExcelExport { } else { cell.setCellValue(cellModel.getText()); } - cellStyle.ifPresent(cs -> cell.setCellStyle(cs)); + cell.setCellStyle(cellStyleToUse); return cell; } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java index d738452ba0..dce61185fe 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -70,10 +70,7 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { @Override public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { Row row = sheet.createRow(rowStart); - ExcelExport.createCell(row, colStart, item, - item.getExcelFormatString() == null - ? Optional.empty() - : Optional.of(ExcelExport.createCellStyle(env.getParentWorkbook(), item.getExcelFormatString()))); + ExcelExport.createCell(env, row, colStart, item, Optional.empty()); return new ItemDimensions(rowStart, colStart, rowStart, colStart); } } @@ -95,11 +92,8 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { @Override public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { Row row = sheet.createRow(rowStart); - ExcelExport.createCell(row, colStart, key, Optional.of(env.getHeaderStyle())); - ExcelExport.createCell(row, colStart + 1, value, - value.getExcelFormatString() == null - ? Optional.empty() - : Optional.of(ExcelExport.createCellStyle(env.getParentWorkbook(), value.getExcelFormatString()))); + ExcelExport.createCell(env, row, colStart, key, Optional.of(env.getHeaderStyle())); + ExcelExport.createCell(env, row, colStart + 1, value, Optional.empty()); return new ItemDimensions(rowStart, colStart, rowStart, colStart + 1); } } @@ -126,7 +120,7 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { @Override public ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException { - ExcelExport.createCell(sheet.createRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle())); + ExcelExport.createCell(env, sheet.createRow(rowStart), colStart, new DefaultCellModel<>(title), Optional.of(env.getHeaderStyle())); int curRow = rowStart + 1; int maxCol = colStart; for (ExcelItemExportable export : children) { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index 887ed6f323..9a10a1541c 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -129,18 +129,14 @@ public class ExcelTableExport implements ExcelSheet // freeze header row sheet.createFreezePane(0, 1); // Create Cell Style for each column (if one is needed) - Map cellStyles = new HashMap<>(); + for (int rowNum = 0; rowNum < safeData.size(); rowNum++) { T rowData = safeData.get(rowNum); Row row = sheet.createRow(rowNum + rowStart + 1); for (int colNum = 0; colNum < columns.size(); colNum++) { ColumnModel colModel = columns.get(colNum); ExcelCellModel cellModel = colModel.getCellRenderer().apply(rowData); - String formatString = cellModel.getExcelFormatString(); - Optional cellStyle = (formatString == null) - ? Optional.empty() - : Optional.of(cellStyles.computeIfAbsent(formatString, k -> ExcelExport.createCellStyle(worksheetEnv.getParentWorkbook(), formatString))); - ExcelExport.createCell(row, colNum + colStart, cellModel, cellStyle); + ExcelExport.createCell(worksheetEnv, row, colNum + colStart, cellModel, Optional.empty()); } } From 26091c4c583a8e6a7f15df42bb996a8b486e78c6 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Tue, 16 Mar 2021 15:32:25 -0400 Subject: [PATCH 12/14] centralize alignment --- .../autopsy/datasourcesummary/uiutils/ExcelExport.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java index 3e2cbfabe3..7472bdada7 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java @@ -195,12 +195,13 @@ public class ExcelExport { //headerFont.setFontHeightInPoints((short) 14); // Create a CellStyle with the font + HorizontalAlignment alignment = HorizontalAlignment.LEFT; CellStyle headerCellStyle = workbook.createCellStyle(); headerCellStyle.setFont(headerFont); - headerCellStyle.setAlignment(HorizontalAlignment.LEFT); + headerCellStyle.setAlignment(alignment); CellStyle defaultCellStyle = workbook.createCellStyle(); - defaultCellStyle.setAlignment(HorizontalAlignment.LEFT); + defaultCellStyle.setAlignment(alignment); WorksheetEnv env = new WorksheetEnv(headerCellStyle, defaultCellStyle, workbook); From 72c1e5a224bc6cb482eb20339b6b0ebf2379c73a Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 17 Mar 2021 11:50:02 -0400 Subject: [PATCH 13/14] commenting --- .../ui/BaseDataSourceSummaryPanel.java | 2 +- .../datasourcesummary/ui/ContainerPanel.java | 169 +++++++++++++++--- .../ui/DataSourceSummaryTabbedPane.java | 12 +- .../ui/IngestJobExcelExport.java | 81 ++++++++- .../ui/SizeRepresentationUtil.java | 32 +++- .../datasourcesummary/ui/TypesPanel.java | 24 ++- .../datasourcesummary/uiutils/CellModel.java | 19 +- .../uiutils/DefaultCellModel.java | 8 +- .../uiutils/ExcelExport.java | 137 +++++++++++--- .../uiutils/ExcelSpecialFormatExport.java | 127 +++++++++++-- .../uiutils/ExcelTableExport.java | 29 ++- 11 files changed, 530 insertions(+), 110 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java index 818dc85cdd..35d65db932 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/BaseDataSourceSummaryPanel.java @@ -47,7 +47,7 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.EventUpdateHandler; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.DefaultMenuItem; import org.sleuthkit.autopsy.datasourcesummary.uiutils.GuiCellModel.MenuItem; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java index affba43a61..a8e5fef64e 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/ContainerPanel.java @@ -37,7 +37,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; import static org.sleuthkit.autopsy.datasourcesummary.ui.BaseDataSourceSummaryPanel.getFetchResult; -import org.sleuthkit.autopsy.datasourcesummary.ui.SizeRepresentationUtil.SizeUnit; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchResult.ResultType; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetchWorker.DataFetchComponents; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DataFetcher; @@ -45,6 +44,7 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultCellModel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultUpdateGovernor; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.KeyValueItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.SingleCellExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.TitledExportable; @@ -61,6 +61,9 @@ import org.sleuthkit.datamodel.TskCoreException; }) class ContainerPanel extends BaseDataSourceSummaryPanel { + /** + * View model data for data source images. + */ private static class ImageViewModel { private final long unallocatedSize; @@ -75,7 +78,20 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { private final String sha1Hash; private final String sha256Hash; - public ImageViewModel(long unallocatedSize, long size, long sectorSize, + /** + * Main constructor. + * + * @param unallocatedSize Size in bytes of unallocated space. + * @param size Total size in bytes. + * @param sectorSize Sector size in bytes. + * @param timeZone The time zone. + * @param imageType The type of image. + * @param paths The source paths for the image. + * @param md5Hash The md5 hash or null. + * @param sha1Hash The sha1 hash or null. + * @param sha256Hash The sha256 hash or null. + */ + ImageViewModel(long unallocatedSize, long size, long sectorSize, String timeZone, String imageType, List paths, String md5Hash, String sha1Hash, String sha256Hash) { this.unallocatedSize = unallocatedSize; @@ -89,44 +105,73 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { this.sha256Hash = sha256Hash; } - public long getUnallocatedSize() { + /** + * @return Size in bytes of unallocated space. + */ + long getUnallocatedSize() { return unallocatedSize; } - public long getSize() { + /** + * @return Total size in bytes. + */ + long getSize() { return size; } - public long getSectorSize() { + /** + * @return Sector size in bytes. + */ + long getSectorSize() { return sectorSize; } - public String getTimeZone() { + /** + * @return The time zone. + */ + String getTimeZone() { return timeZone; } - public String getImageType() { + /** + * @return The type of image. + */ + String getImageType() { return imageType; } - public List getPaths() { + /** + * @return The source paths for the image. + */ + List getPaths() { return paths; } - public String getMd5Hash() { + /** + * @return The md5 hash or null. + */ + String getMd5Hash() { return md5Hash; } - public String getSha1Hash() { + /** + * @return The sha1 hash or null. + */ + String getSha1Hash() { return sha1Hash; } - public String getSha256Hash() { + /** + * @return The sha256 hash or null. + */ + String getSha256Hash() { return sha256Hash; } - } + /** + * View model for container data. + */ private static class ContainerViewModel { private final String displayName; @@ -135,6 +180,17 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { private final String acquisitionDetails; private final ImageViewModel imageViewModel; + /** + * Main constructor. + * + * @param displayName The display name for this data source. + * @param originalName The original name for this data source. + * @param deviceIdValue The device id value for this data source. + * @param acquisitionDetails The acquisition details for this data + * source or null. + * @param imageViewModel If the data source is an image, the image view + * model for this data source or null if non-image. + */ ContainerViewModel(String displayName, String originalName, String deviceIdValue, String acquisitionDetails, ImageViewModel imageViewModel) { this.displayName = displayName; @@ -144,22 +200,38 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { this.imageViewModel = imageViewModel; } + /** + * @return The display name for this data source. + */ String getDisplayName() { return displayName; } + /** + * @return The original name for this data source. + */ String getOriginalName() { return originalName; } + /** + * @return The device id value for this data source. + */ String getDeviceId() { return deviceIdValue; } + /** + * @return The acquisition details for this data source or null. + */ String getAcquisitionDetails() { return acquisitionDetails; } + /** + * @return If the data source is an image, the image view model for this + * data source or null if non-image. + */ ImageViewModel getImageViewModel() { return imageViewModel; } @@ -241,11 +313,29 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { fetchInformation(dataFetchComponents, dataSource); } + /** + * A means of retrieving data that could potentially throw an exception. + */ private interface Retriever { + /** + * Retrieves data of a certain type and possibly throws an exception. + * + * @return The data type. + * @throws TskCoreException + * @throws SleuthkitCaseProviderException + * @throws SQLException + */ O retrieve() throws TskCoreException, SleuthkitCaseProviderException, SQLException; } + /** + * Retrieves data of a particular type and handles any exceptions that may + * be thrown by logging. + * + * @param retriever The retrieving function. + * @return The retrieved data. + */ private static O retrieve(Retriever retriever) { try { return retriever.retrieve(); @@ -255,6 +345,14 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { } } + /** + * Generates a container view model object containing data to display about + * the data source. + * + * @param containerSummary The service providing data about the data source. + * @param ds The data source. + * @return The generated view model. + */ private static ContainerViewModel getContainerViewModel(ContainerSummary containerSummary, DataSource ds) { if (ds == null) { return null; @@ -269,6 +367,14 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ); } + /** + * Generates an image view model object containing data to display about the + * image. + * + * @param containerSummary The service providing data about the image. + * @param image The image. + * @return The generated view model. + */ private static ImageViewModel getImageViewModel(ContainerSummary containerSummary, Image image) { if (image == null) { return null; @@ -287,6 +393,11 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { return new ImageViewModel(unallocSize, size, sectorSize, timeZone, imageType, paths, md5, sha1, sha256); } + /** + * Update the swing components with fetched data. + * + * @param viewModel The data source view model data. + */ private void updateDetailsPanelData(ContainerViewModel viewModel) { clearTableValues(); if (viewModel == null) { @@ -307,6 +418,9 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { this.repaint(); } + /** + * Sets image-only fields to N/A. + */ @Messages({ "ContainerPanel_setFieldsForNonImageDataSource_na=N/A" }) @@ -326,6 +440,11 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { sha256HashValue.setText(NA); } + /** + * Sets fields for images. + * + * @param viewModel The image view model data. + */ private void setFieldsForImage(ImageViewModel viewModel) { unallocatedSizeValue.setText(SizeRepresentationUtil.getSizeString(viewModel.getUnallocatedSize())); imageTypeValue.setText(viewModel.getImageType()); @@ -361,29 +480,25 @@ class ContainerPanel extends BaseDataSourceSummaryPanel { ((DefaultTableModel) filePathsTable.getModel()).setRowCount(0); } - private static List getAcquisitionDetails(String acquisitionDetails) { + /** + * Divides acquisition details into key/value pairs to be displayed in + * separate cells in an excel export. + * + * @param acquisitionDetails The acquisition details. + * @return The list of key value pairs that can be incorporated into the + * excel export. + */ + private static List getAcquisitionDetails(String acquisitionDetails) { if (StringUtils.isBlank(acquisitionDetails)) { return Collections.emptyList(); } else { return Stream.of(acquisitionDetails.split("\\r?\\n")) - .map((line) -> { - if (StringUtils.isBlank(line)) { - return null; - } else { - int colonIdx = line.indexOf(':'); - if (colonIdx >= 0) { - return new KeyValueItemExportable(new DefaultCellModel<>(line.substring(0, colonIdx + 1).trim()), - new DefaultCellModel<>(line.substring(colonIdx + 1, line.length()).trim())); - } else { - return new KeyValueItemExportable(new DefaultCellModel<>(""), new DefaultCellModel<>(line)); - } - } - }) + .map((line) -> (StringUtils.isBlank(line)) ? null : new SingleCellExportable(line)) .filter(item -> item != null) .collect(Collectors.toList()); } } - + @Override @Messages({ "ContainerPanel_export_displayName=Display Name:", diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java index c65e5e39cb..4a77a8a485 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryTabbedPane.java @@ -68,7 +68,6 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { * * @param tabTitle The title of the tab. * @param panel The component to be displayed in the tab. - * @param notifyParentClose Notifies parent to trigger a close. */ DataSourceTab(String tabTitle, BaseDataSourceSummaryPanel panel) { this(tabTitle, panel, panel::setDataSource, panel::getExports, panel::close); @@ -138,10 +137,10 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { private Runnable notifyParentClose = null; private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel(); - + // create an export panel whose button triggers the export to XLSX action private final ExportPanel exportPanel = new ExportPanel(); - + private final List tabs = Arrays.asList( new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_typesTab_title(), new TypesPanel()), new DataSourceTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), new UserActivityPanel()), @@ -165,10 +164,10 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { null, null) ); - + // the action that does the export private final ExcelExportAction exportAction = new ExcelExportAction(tabs); - + private DataSource dataSource = null; private CardLayout cardLayout; @@ -222,7 +221,7 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { // set this to no datasource initially cardLayout.show(this, NO_DATASOURCE_PANE); - + // set action for when user requests xlsx export exportPanel.setXlsxExportAction(() -> exportAction.accept(getDataSource())); } @@ -270,7 +269,6 @@ public class DataSourceSummaryTabbedPane extends javax.swing.JPanel { Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), caseEventsListener); } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java index 51e0407286..3ad757e7be 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/IngestJobExcelExport.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2021 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.datasourcesummary.ui; @@ -17,8 +30,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -33,8 +44,7 @@ import org.sleuthkit.datamodel.IngestModuleInfo; import org.sleuthkit.datamodel.TskCoreException; /** - * - * @author gregd + * Class that handles exporting information in IngestJobInfoPanel to excel. */ @Messages({ "IngestJobExcelExport_startTimeColumn=Start Time", @@ -46,6 +56,9 @@ import org.sleuthkit.datamodel.TskCoreException; }) class IngestJobExcelExport { + /** + * An entry to display in an excel export. + */ private static class IngestJobEntry { private final Date startTime; @@ -54,6 +67,15 @@ class IngestJobExcelExport { private final String ingestModule; private final String ingestModuleVersion; + /** + * Main constructor. + * + * @param startTime The ingest start time. + * @param endTime The ingest stop time. + * @param status The ingest status. + * @param ingestModule The ingest module. + * @param ingestModuleVersion The ingest module version. + */ IngestJobEntry(Date startTime, Date endTime, String status, String ingestModule, String ingestModuleVersion) { this.startTime = startTime; this.endTime = endTime; @@ -62,22 +84,37 @@ class IngestJobExcelExport { this.ingestModuleVersion = ingestModuleVersion; } + /** + * @return The ingest start time. + */ Date getStartTime() { return startTime; } + /** + * @return The ingest stop time. + */ Date getEndTime() { return endTime; } + /** + * @return The ingest status. + */ String getStatus() { return status; } + /** + * @return The ingest module. + */ String getIngestModule() { return ingestModule; } + /** + * @return The ingest module version. + */ String getIngestModuleVersion() { return ingestModuleVersion; } @@ -87,6 +124,7 @@ class IngestJobExcelExport { private static final String DATETIME_FORMAT_STR = "yyyy/MM/dd HH:mm:ss"; private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat(DATETIME_FORMAT_STR, Locale.getDefault()); + // columns in the excel export table to be created. private static final List>> COLUMNS = Arrays.asList( new ColumnModel<>( Bundle.IngestJobExcelExport_startTimeColumn(), @@ -105,11 +143,23 @@ class IngestJobExcelExport { (entry) -> new DefaultCellModel<>(entry.getIngestModuleVersion())) ); + /** + * Retrieves data for a date cell. + * + * @param date The date. + * @return The data cell to be used in the excel export. + */ private static DefaultCellModel getDateCell(Date date) { Function dateParser = (dt) -> dt == null ? "" : DATETIME_FORMAT.format(dt); return new DefaultCellModel<>(date, dateParser, DATETIME_FORMAT_STR); } + /** + * Retrieves all the ingest job modules and versions for a job. + * + * @param job The ingest job. + * @return All of the corresponding entries sorted by module name. + */ private static List getEntries(IngestJobInfo job) { List infoList = job.getIngestModuleInfo(); if (infoList == null) { @@ -135,6 +185,13 @@ class IngestJobExcelExport { } } + /** + * For output, show ingest job details in first row present. Otherwise, set + * to null. + * + * @param list The list of entries for an ingest job. + * @return The stream of entries to be displayed. + */ private static Stream showFirstRowOnly(List list) { return IntStream.range(0, list.size()) .mapToObj(idx -> { @@ -148,6 +205,12 @@ class IngestJobExcelExport { } + /** + * Returns a list of sheets to be exported for the Ingest History tab. + * + * @param dataSource The data source. + * @return The list of sheets to be included in an export. + */ static List getExports(DataSource dataSource) { if (dataSource == null) { return Collections.emptyList(); @@ -167,6 +230,7 @@ class IngestJobExcelExport { List toDisplay = info.stream() .filter(job -> job != null && dataSource.getId() == job.getObjectId()) .sorted((a, b) -> { + // sort ingest jobs by time. boolean aIsNull = a.getStartDateTime() == null; boolean bIsNull = b.getStartDateTime() == null; if (aIsNull || bIsNull) { @@ -183,4 +247,7 @@ class IngestJobExcelExport { return Arrays.asList(new ExcelTableExport<>(Bundle.IngestJobExcelExport_sheetName(), COLUMNS, toDisplay)); } + + private IngestJobExcelExport() { + } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java index ef30e790bb..74b9be06a8 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/SizeRepresentationUtil.java @@ -33,7 +33,9 @@ public final class SizeRepresentationUtil { private static final int SIZE_CONVERSION_CONSTANT = 1000; private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##"); - // based on https://www.mrexcel.com/board/threads/how-do-i-format-cells-to-show-gb-mb-kb.140135/ + /** + * A size unit corresponding to orders of magnitude of bytes (kilobyte, gigabytes, etc.). + */ @NbBundle.Messages({ "SizeRepresentationUtil_units_bytes=bytes", "SizeRepresentationUtil_units_kilobytes=KB", @@ -54,20 +56,37 @@ public final class SizeRepresentationUtil { private final String excelFormatString; private final long divisor; + /** + * Main constructor. + * @param suffix The string suffix to use for size unit. + * @param excelFormatString The excel format string to use for this size unit. + * @param power The power of 1000 of bytes for this size unit. + */ SizeUnit(String suffix, String excelFormatString, int power) { this.suffix = suffix; + + // based on https://www.mrexcel.com/board/threads/how-do-i-format-cells-to-show-gb-mb-kb.140135/ this.excelFormatString = String.format("%s \"%s\"", excelFormatString, suffix); this.divisor = (long) Math.pow(SIZE_CONVERSION_CONSTANT, power); } + /** + * @return The string suffix to use for size unit. + */ public String getSuffix() { return suffix; } + /** + * @return The excel format string to use for this size unit. + */ public String getExcelFormatString() { return excelFormatString; } + /** + * @return The divisor to convert from bytes to this unit. + */ public long getDivisor() { return divisor; } @@ -85,6 +104,11 @@ public final class SizeRepresentationUtil { return getSizeString(size, APPROXIMATE_SIZE_FORMAT, true); } + /** + * Determines the relevant size unit that should be used for a particular size. + * @param size The size in bytes. + * @return The relevant size unit. + */ static SizeUnit getSizeUnit(Long size) { if (size == null) { return SizeUnit.values()[0]; @@ -135,7 +159,11 @@ public final class SizeRepresentationUtil { } } - + /** + * Returns a default cell model using size units. + * @param bytes The number of bytes. + * @return The default cell model. + */ static DefaultCellModel getBytesCell(Long bytes) { if (bytes == null) { return new DefaultCellModel<>(""); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java index c7e90e5072..97fc63fc51 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/TypesPanel.java @@ -31,7 +31,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.FileTypeUtils.FileTypeCategory; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TypesSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.ContainerSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.MimeTypeSummary; @@ -50,7 +49,6 @@ import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableComponent; import org.sleuthkit.autopsy.datasourcesummary.uiutils.LoadableLabel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel; import org.sleuthkit.autopsy.datasourcesummary.uiutils.PieChartPanel.PieChartItem; -import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TskCoreException; @@ -173,9 +171,8 @@ class TypesPanel extends BaseDataSourceSummaryPanel { private static final long serialVersionUID = 1L; private static final DecimalFormat INTEGER_SIZE_FORMAT = new DecimalFormat("#"); private static final String COMMA_FORMAT_STR = "#,###"; - + private static final DecimalFormat COMMA_FORMATTER = new DecimalFormat(COMMA_FORMAT_STR); - private static final Logger logger = Logger.getLogger(TypesPanel.class.getName()); private static final Color IMAGES_COLOR = new Color(156, 39, 176); private static final Color VIDEOS_COLOR = Color.YELLOW; @@ -412,14 +409,31 @@ class TypesPanel extends BaseDataSourceSummaryPanel { return longVal == null ? "0" : COMMA_FORMATTER.format(longVal); } + /** + * Returns a key value pair to be exported in a sheet. + * + * @param fetcher The means of fetching the data. + * @param key The key to use. + * @param dataSource The data source containing the data. + * @return The key value pair to be exported. + */ private static KeyValueItemExportable getStrExportable(DataFetcher fetcher, String key, DataSource dataSource) { String result = getFetchResult(fetcher, "Types", dataSource); return (result == null) ? null : new KeyValueItemExportable(key, new DefaultCellModel<>(result)); } + /** + * Returns a key value pair to be exported in a sheet formatting the long + * with commas separated by orders of 1000. + * + * @param fetcher The means of fetching the data. + * @param key The string key for this key value pair. + * @param dataSource The data source. + * @return The key value pair. + */ private static KeyValueItemExportable getCountExportable(DataFetcher fetcher, String key, DataSource dataSource) { Long count = getFetchResult(fetcher, "Types", dataSource); - return (count == null) ? null : new KeyValueItemExportable(key, + return (count == null) ? null : new KeyValueItemExportable(key, new DefaultCellModel(count, COMMA_FORMATTER::format, COMMA_FORMAT_STR)); } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModel.java index 38afc21e2f..60d7e27112 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/CellModel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils; import javax.swing.JLabel; +import org.apache.poi.ss.usermodel.HorizontalAlignment; /** * Basic interface for a cell model. @@ -29,20 +30,23 @@ public interface CellModel { * Describes the horizontal alignment. */ public enum HorizontalAlign { - LEFT(JLabel.LEFT), - CENTER(JLabel.CENTER), - RIGHT(JLabel.RIGHT); + LEFT(JLabel.LEFT, HorizontalAlignment.LEFT), + CENTER(JLabel.CENTER, HorizontalAlignment.CENTER), + RIGHT(JLabel.RIGHT, HorizontalAlignment.RIGHT); private final int jlabelAlignment; + private final HorizontalAlignment poiAlignment; /** * Constructor for a HorizontalAlign enum. * * @param jlabelAlignment The corresponding JLabel horizontal alignment * number. + * @param poiAlignment Horizontal alignment for Apache POI. */ - HorizontalAlign(int jlabelAlignment) { + HorizontalAlign(int jlabelAlignment, HorizontalAlignment poiAlignment) { this.jlabelAlignment = jlabelAlignment; + this.poiAlignment = poiAlignment; } /** @@ -52,6 +56,13 @@ public interface CellModel { int getJLabelAlignment() { return this.jlabelAlignment; } + + /** + * @return Horizontal alignment for Apache POI. + */ + HorizontalAlignment getPoiAlignment() { + return poiAlignment; + } } /** diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java index 009405bf4b..06165a3023 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/DefaultCellModel.java @@ -24,21 +24,21 @@ import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelCellModel; /** * The default cell model. */ public class DefaultCellModel implements GuiCellModel, ExcelCellModel { - private final T data; - private final Function stringConverter; + final T data; + final Function stringConverter; String tooltip; CellModel.HorizontalAlign horizontalAlignment; Insets insets; List popupMenu; Supplier> menuItemSupplier; - private final String excelFormatString; + final String excelFormatString; /** * Main constructor. diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java index 7472bdada7..fab6558c4a 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelExport.java @@ -26,8 +26,8 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import org.apache.commons.lang3.tuple.Pair; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Font; @@ -37,6 +37,7 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datasourcesummary.uiutils.CellModel.HorizontalAlign; /** * Class for handling Excel exporting. @@ -68,6 +69,87 @@ public class ExcelExport { } } + /** + * A cell style key that can be used with the WorksheetEnv to generate a + * cell style to be used in a POI excel document. + */ + static class CellStyleKey { + + private final String formatString; + private final CellStyle cellStyle; + private final HorizontalAlign alignment; + + /** + * Main constructor. + * + * @param formatString The format string or null if no special + * formatting. + * @param cellStyle The base cell style or null if default is to be + * used. + * @param alignment The horizontal alignment or null if default is to be + * used. + */ + CellStyleKey(String formatString, CellStyle cellStyle, HorizontalAlign alignment) { + this.formatString = formatString; + this.cellStyle = cellStyle; + this.alignment = alignment; + } + + /** + * @return The format string or null if no special formatting. + */ + String getFormatString() { + return formatString; + } + + /** + * @return The base cell style or null if default is to be used. + */ + CellStyle getCellStyle() { + return cellStyle; + } + + /** + * @return The horizontal alignment or null if default is to be used. + */ + HorizontalAlign getAlignment() { + return alignment; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.formatString); + hash = 29 * hash + Objects.hashCode(this.cellStyle); + hash = 29 * hash + Objects.hashCode(this.alignment); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CellStyleKey other = (CellStyleKey) obj; + if (!Objects.equals(this.formatString, other.formatString)) { + return false; + } + if (!Objects.equals(this.cellStyle, other.cellStyle)) { + return false; + } + if (this.alignment != other.alignment) { + return false; + } + return true; + } + } + /** * Class detailing aspects of the worksheet. */ @@ -76,10 +158,10 @@ public class ExcelExport { private final CellStyle headerStyle; private final Workbook parentWorkbook; private final CellStyle defaultStyle; - + // maps a data format string / original cell style combination to a created cell style - private final Map, CellStyle> cellStyleCache = new HashMap<>(); - + private final Map cellStyleCache = new HashMap<>(); + /** * Main constructor. * @@ -92,17 +174,29 @@ public class ExcelExport { this.defaultStyle = defaultStyle; this.parentWorkbook = parentWorkbook; } - - - public CellStyle getCellStyle(CellStyle baseStyle, String dataFormat) { - return cellStyleCache.computeIfAbsent(Pair.of(dataFormat, baseStyle), (pair) -> { + + /** + * Returns a cell style signified by the given cell style key. If the + * key is already present, a cached version is returned. + * + * @param cellStyleKey The key. + * @return The cell style representing this key. + */ + public CellStyle getCellStyle(CellStyleKey cellStyleKey) { + return cellStyleCache.computeIfAbsent(cellStyleKey, (pair) -> { CellStyle computed = this.parentWorkbook.createCellStyle(); - computed.cloneStyleFrom(pair.getRight() == null ? defaultStyle : pair.getRight()); - computed.setDataFormat(this.parentWorkbook.getCreationHelper().createDataFormat().getFormat(dataFormat)); + computed.cloneStyleFrom(cellStyleKey.getCellStyle() == null ? defaultStyle : cellStyleKey.getCellStyle()); + + if (cellStyleKey.getAlignment() != null) { + computed.setAlignment(cellStyleKey.getAlignment().getPoiAlignment()); + } + + if (cellStyleKey.getFormatString() != null) { + computed.setDataFormat(this.parentWorkbook.getCreationHelper().createDataFormat().getFormat(cellStyleKey.getFormatString())); + } return computed; }); } - /** * Returns the cell style to use for headers. @@ -115,13 +209,13 @@ public class ExcelExport { /** * Returns the cell style for default items. - * + * * @return The cell style for default items. */ public CellStyle getDefaultCellStyle() { return defaultStyle; } - + /** * Returns the parent workbook. * @@ -160,6 +254,7 @@ public class ExcelExport { /** * Retrieves a singleton instance of this class. + * * @return The instance. */ public static ExcelExport getInstance() { @@ -176,10 +271,11 @@ public class ExcelExport { /** * Writes the exports to a workbook. + * * @param exports The sheets to export. * @param path The path to the output file. * @throws IOException - * @throws ExcelExportException + * @throws ExcelExportException */ @Messages({ "# {0} - sheetNumber", @@ -199,7 +295,7 @@ public class ExcelExport { CellStyle headerCellStyle = workbook.createCellStyle(); headerCellStyle.setFont(headerFont); headerCellStyle.setAlignment(alignment); - + CellStyle defaultCellStyle = workbook.createCellStyle(); defaultCellStyle.setAlignment(alignment); @@ -231,7 +327,6 @@ public class ExcelExport { workbook.close(); } - /** * Creates an excel cell given the model. * @@ -242,13 +337,13 @@ public class ExcelExport { * @param cellStyle The style to use. * @return The created cell. */ - static Cell createCell(WorksheetEnv env, Row row, int colNum, ExcelTableExport.ExcelCellModel cellModel, Optional cellStyle) { + static Cell createCell(WorksheetEnv env, Row row, int colNum, ExcelCellModel cellModel, Optional cellStyle) { CellStyle cellStyleToUse = cellStyle.orElse(env.getDefaultCellStyle()); - - if (cellModel.getExcelFormatString() != null) { - cellStyleToUse = env.getCellStyle(cellStyleToUse, cellModel.getExcelFormatString()); + + if (cellModel.getExcelFormatString() != null || cellModel.getHorizontalAlignment() != null) { + cellStyleToUse = env.getCellStyle(new CellStyleKey(cellModel.getExcelFormatString(), cellStyleToUse, cellModel.getHorizontalAlignment())); } - + Object cellData = cellModel.getData(); Cell cell = row.createCell(colNum); if (cellData instanceof Calendar) { diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java index dce61185fe..f9c46fa5e2 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelSpecialFormatExport.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2021 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.datasourcesummary.uiutils; @@ -11,14 +24,16 @@ import java.util.Optional; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; /** - * - * @author gregd + * An excel export that has special row-by-row formatting. */ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { + /** + * The dimensions consumed by an item in an ExcelSpecialFormatExport list of + * items to be rendered. + */ public static class ItemDimensions { private final int rowStart; @@ -26,6 +41,14 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { private final int colStart; private final int colEnd; + /** + * Main constructor. + * + * @param rowStart The starting excel row of the item. + * @param colStart The starting excel column of the item. + * @param rowEnd The last excel row of the the item. + * @param colEnd The last excel column of the item. + */ public ItemDimensions(int rowStart, int colStart, int rowEnd, int colEnd) { this.rowStart = rowStart; this.colStart = colStart; @@ -33,36 +56,74 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { this.colEnd = colEnd; } + /** + * @return The starting excel row of the item. + */ public int getRowStart() { return rowStart; } + /** + * @return The last excel row of the the item. + */ public int getRowEnd() { return rowEnd; } + /** + * @return The starting excel column of the item. + */ public int getColStart() { return colStart; } + /** + * @return The last excel column of the item. + */ public int getColEnd() { return colEnd; } } + /** + * An item to be exported in a specially formatted excel export. + */ public interface ExcelItemExportable { + /** + * Writes the item to the sheet in the special format export sheet. + * + * @param sheet The sheet. + * @param rowStart The starting row to start writing. + * @param colStart The starting column to start writing. + * @param env The excel export context. + * @return The dimensions of what has been written. + * @throws ExcelExportException + */ ItemDimensions write(Sheet sheet, int rowStart, int colStart, ExcelExport.WorksheetEnv env) throws ExcelExportException; } + /** + * Writes a string to a single cell in a specially formatted excel export. + */ public static class SingleCellExportable implements ExcelItemExportable { private final ExcelCellModel item; + /** + * Main constructor. + * + * @param key The text to be written. + */ public SingleCellExportable(String key) { this(new DefaultCellModel<>(key)); } + /** + * Main constructor. + * + * @param item The cell model to be written. + */ public SingleCellExportable(ExcelCellModel item) { this.item = item; } @@ -75,15 +136,31 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { } } + /** + * Writes a row consisting of first column as a key and second column as a + * value. + */ public static class KeyValueItemExportable implements ExcelItemExportable { private final ExcelCellModel key; private final ExcelCellModel value; + /** + * Main constructor. + * + * @param key The string key to be exported. + * @param value The cell model to be exported. + */ public KeyValueItemExportable(String key, ExcelCellModel value) { this(new DefaultCellModel<>(key), value); } + /** + * Main constructor. + * + * @param key The cell key to be exported. + * @param value The cell model to be exported. + */ public KeyValueItemExportable(ExcelCellModel key, ExcelCellModel value) { this.key = key; this.value = value; @@ -98,14 +175,17 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { } } - private final String sheetName; - private final List exports; - - public ExcelSpecialFormatExport(String sheetName, List exports) { - this.sheetName = sheetName; - this.exports = exports == null ? Collections.emptyList() : exports; - } - + /** + * A special format excel export item that shows a title and a list of items + * indented one column. + * + * i.e. + *
+     * title
+     *      item 1
+     *      item 2
+     * 
+ */ public static class TitledExportable implements ExcelItemExportable { private static final int DEFAULT_INDENT = 1; @@ -113,6 +193,12 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { private final String title; private final List children; + /** + * Main constructor. + * + * @param title The title for the export. + * @param children The children to be indented and enumerated. + */ public TitledExportable(String title, List children) { this.title = title; this.children = children; @@ -135,7 +221,20 @@ public class ExcelSpecialFormatExport implements ExcelExport.ExcelSheetExport { return new ItemDimensions(rowStart, colStart, curRow - 1, maxCol); } + } + private final String sheetName; + private final List exports; + + /** + * Main constructor. + * + * @param sheetName The name of the sheet. + * @param exports The row-by-row items to be exported. + */ + public ExcelSpecialFormatExport(String sheetName, List exports) { + this.sheetName = sheetName; + this.exports = exports == null ? Collections.emptyList() : exports; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java index 9a10a1541c..5592344e2c 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelTableExport.java @@ -19,37 +19,21 @@ package org.sleuthkit.autopsy.datasourcesummary.uiutils; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelExportException; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelExport.ExcelSheetExport; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ExcelItemExportable; import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelSpecialFormatExport.ItemDimensions; -import org.sleuthkit.autopsy.datasourcesummary.uiutils.ExcelTableExport.ExcelCellModel; /** * An excel sheet export of table data. */ public class ExcelTableExport implements ExcelSheetExport, ExcelItemExportable { - /** - * Basic interface for a cell model. - */ - public interface ExcelCellModel extends CellModel { - - /** - * @return The format string to be used with Apache POI during excel - * export or null if none necessary. - */ - String getExcelFormatString(); - } - private final String sheetName; private final List> columns; private final List data; @@ -66,7 +50,16 @@ public class ExcelTableExport implements ExcelSheet public ExcelTableExport(String sheetName, List> columns, List data) { this(sheetName, columns, data, 0); } - + + /** + * Main constructor. + * + * @param sheetName The name of the sheet. NOTE: There can be no duplicates + * in a workbook. + * @param columns The columns of the table. + * @param data The data to export. + * @param columnIndent The column indent. + */ public ExcelTableExport(String sheetName, List> columns, List data, int columnIndent) { this.sheetName = sheetName; this.columns = columns; @@ -129,7 +122,7 @@ public class ExcelTableExport implements ExcelSheet // freeze header row sheet.createFreezePane(0, 1); // Create Cell Style for each column (if one is needed) - + for (int rowNum = 0; rowNum < safeData.size(); rowNum++) { T rowData = safeData.get(rowNum); Row row = sheet.createRow(rowNum + rowStart + 1); From 3563ca6b7323519ae4876cafa798a7066a26452e Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 17 Mar 2021 11:50:09 -0400 Subject: [PATCH 14/14] commenting --- .../uiutils/ExcelCellModel.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelCellModel.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelCellModel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelCellModel.java new file mode 100644 index 0000000000..0ca52f12e4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/ExcelCellModel.java @@ -0,0 +1,32 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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.datasourcesummary.uiutils; + +/** + * Basic interface for a cell model. + */ +public interface ExcelCellModel extends CellModel { + + /** + * @return The format string to be used with Apache POI during excel + * export or null if none necessary. + */ + String getExcelFormatString(); + +}