diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java index b72bc14330..1f5f6e362f 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceInfoUtilities.java @@ -108,7 +108,24 @@ final class DataSourceInfoUtilities { * obtained. */ static T getBaseQueryResult(String query, ResultSetHandler processor, String errorMessage) { - try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) { + return getBaseQueryResult(SleuthkitCaseProvider.DEFAULT, query, processor, errorMessage); + } + + + /** + * Retrieves a result based on the provided query. + * + * @param provider The means of obtaining a SleuthkitCase. + * @param query The query. + * @param processor The result set handler. + * @param errorMessage The error message to display if there is an error + * retrieving the resultset. + * + * @return The ResultSetHandler value or null if no ResultSet could be + * obtained. + */ + static T getBaseQueryResult(SleuthkitCaseProvider provider, String query, ResultSetHandler processor, String errorMessage) { + try (SleuthkitCase.CaseDbQuery dbQuery = provider.get().executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); try { return processor.process(resultSet); diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java new file mode 100644 index 0000000000..d61a21e307 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopDomainsSummary.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourcesummary.datamodel; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.sleuthkit.datamodel.DataSource; + +/** + * Provides summary information about top domains in a datasource. + */ +public class DataSourceTopDomainsSummary { + private static final long SLEEP_TIME = 5000; + +// private final SleuthkitCaseProvider provider; +// +// public DataSourceTopDomainsSummary() { +// this(SleuthkitCaseProvider.DEFAULT); +// } +// +// public DataSourceTopDomainsSummary(SleuthkitCaseProvider provider) { +// this.provider = provider; +// } + + interface Function2 { + O apply(A1 a1, A2 a2); + } + + public List getRecentDomains(DataSource dataSource, int count) throws InterruptedException { + Thread.sleep(SLEEP_TIME); + final String dId = Long.toString(dataSource.getId()); + final Function2 getId = (s,idx) -> String.format("d:%s, f:%s, i:%d", dId, s, idx); + return IntStream.range(0, count) + .mapToObj(num -> new TopDomainsResult( + getId.apply("domain", num), + getId.apply("url", num), + (long)num, + new Date(120, 1, num) + )) + .collect(Collectors.toList()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopProgramsSummary.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopProgramsSummary.java index 7c6927cd0d..8bc527f2bb 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopProgramsSummary.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/DataSourceTopProgramsSummary.java @@ -66,6 +66,36 @@ public class DataSourceTopProgramsSummary { */ private static final String QUERY_SUFFIX = "_query"; + /** + * Functions that determine the folder name of a list of path elements. If + * not matched, function returns null. + */ + private static final List, String>> SHORT_FOLDER_MATCHERS = Arrays.asList( + // handle Program Files and Program Files (x86) - if true, return the next folder + (pathList) -> { + if (pathList.size() < 2) { + return null; + } + + String rootParent = pathList.get(0).toUpperCase(); + if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) { + return pathList.get(1); + } else { + return null; + } + }, + // if there is a folder named "APPLICATION DATA" or "APPDATA" + (pathList) -> { + for (String pathEl : pathList) { + String uppered = pathEl.toUpperCase(); + if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) { + return "AppData"; + } + } + return null; + } + ); + /** * Creates a sql statement querying the blackboard attributes table for a * particular attribute type and returning a specified value. That query @@ -138,6 +168,17 @@ public class DataSourceTopProgramsSummary { private static String getLikeClause(String column, String likeString, boolean isLike) { return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'"; } + + + private final SleuthkitCaseProvider provider; + + public DataSourceTopProgramsSummary() { + this(SleuthkitCaseProvider.DEFAULT); + } + + public DataSourceTopProgramsSummary(SleuthkitCaseProvider provider) { + this.provider = provider; + } /** * Retrieves a list of the top programs used on the data source. Currently @@ -149,7 +190,7 @@ public class DataSourceTopProgramsSummary { * * @return */ - public static List getTopPrograms(DataSource dataSource, int count) { + public List getTopPrograms(DataSource dataSource, int count) { if (dataSource == null || count <= 0) { return Collections.emptyList(); } @@ -225,38 +266,9 @@ public class DataSourceTopProgramsSummary { return progResults; }; - return getBaseQueryResult(query, handler, errorMessage); + return getBaseQueryResult(provider, query, handler, errorMessage); } - - /** - * Functions that determine the folder name of a list of path elements. If - * not matched, function returns null. - */ - private static final List, String>> SHORT_FOLDER_MATCHERS = Arrays.asList( - // handle Program Files and Program Files (x86) - if true, return the next folder - (pathList) -> { - if (pathList.size() < 2) { - return null; - } - - String rootParent = pathList.get(0).toUpperCase(); - if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) { - return pathList.get(1); - } else { - return null; - } - }, - // if there is a folder named "APPLICATION DATA" or "APPDATA" - (pathList) -> { - for (String pathEl : pathList) { - String uppered = pathEl.toUpperCase(); - if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) { - return "AppData"; - } - } - return null; - } - ); + /** * Determines a short folder name if any. Otherwise, returns empty string. @@ -289,7 +301,4 @@ public class DataSourceTopProgramsSummary { return ""; } - - private DataSourceTopProgramsSummary() { - } } diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/SleuthkitCaseProvider.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/SleuthkitCaseProvider.java new file mode 100644 index 0000000000..9855fde185 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/SleuthkitCaseProvider.java @@ -0,0 +1,33 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourcesummary.datamodel; + +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * An interface to provide the current SleuthkitCase object. By default, this + * uses Case.getCurrentCaseThrows().getSleuthkkitCase(). + */ +public interface SleuthkitCaseProvider { + public static final SleuthkitCaseProvider DEFAULT = () -> Case.getCurrentCaseThrows().getSleuthkitCase(); + + SleuthkitCase get() throws NoCurrentCaseException; +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TopDomainsResult.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TopDomainsResult.java new file mode 100644 index 0000000000..4d8bc1c012 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/TopDomainsResult.java @@ -0,0 +1,57 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datasourcesummary.datamodel; + +import java.util.Date; + +/** + * Describes a result of a program run on a datasource. + */ +public class TopDomainsResult { + + private final String domain; + private final String url; + private final Long visitTimes; + private final Date lastVisit; + + public TopDomainsResult(String domain, String url, Long visitTimes, Date lastVisit) { + this.domain = domain; + this.url = url; + this.visitTimes = visitTimes; + this.lastVisit = lastVisit; + } + + public String getDomain() { + return domain; + } + + public String getUrl() { + return url; + } + + public Long getVisitTimes() { + return visitTimes; + } + + public Date getLastVisit() { + return lastVisit; + } + + +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties index 462d910dde..708010e07a 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties @@ -35,4 +35,5 @@ DataSourceSummaryDetailsPanel.unallocatedSizeLabel.text=Unallocated Space: DataSourceSummaryDetailsPanel.unallocatedSizeValue.text= DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category DataSourceSummaryCountsPanel.resultsByTypeLabel.text=Results by Type -DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run +DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs +DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains 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 811040a3ca..1d72d08d52 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/Bundle.properties-MERGED @@ -75,8 +75,12 @@ DataSourceSummaryTabbedPane_countsTab_title=Counts DataSourceSummaryTabbedPane_detailsTab_title=Details DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History DataSourceSummaryTabbedPane_userActivityTab_title=User Activity -DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run +DataSourceSummaryUserActivityPanel.programsRunLabel.text=Recent Programs +DataSourceSummaryUserActivityPanel.recentDomainsLabel.text=Recent Domains DataSourceSummaryUserActivityPanel_tab_title=User Activity +DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain +DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access +DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form index 0829b9e045..4b19d7b0e3 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.form @@ -1,6 +1,11 @@
+ + + + + @@ -11,59 +16,180 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - + + + + + - - - - - - - - - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java index 21b2a30f22..29e74b66b8 100644 --- a/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/ui/DataSourceSummaryUserActivityPanel.java @@ -21,20 +21,26 @@ package org.sleuthkit.autopsy.datasourcesummary.ui; import java.awt.Component; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Locale; -import javax.swing.JLabel; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableCellRenderer; +import java.util.stream.Collectors; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopDomainsSummary; import org.sleuthkit.autopsy.datasourcesummary.datamodel.DataSourceTopProgramsSummary; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopDomainsResult; import org.sleuthkit.autopsy.datasourcesummary.datamodel.TopProgramsResult; +import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker; +import org.sleuthkit.autopsy.guiutils.internal.DataFetchWorker.DataFetchComponents; +import org.sleuthkit.autopsy.guiutils.internal.DataLoadingResult; +import org.sleuthkit.autopsy.guiutils.internal.DataResultJTable; +import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel; +import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultCellModel; +import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.DefaultColumnModel; +import org.sleuthkit.autopsy.guiutils.internal.DefaultPojoListTableDataModel.HorizontalAlign; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.autopsy.guiutils.internal.SwingWorkerSequentialRunner; /** * A panel to display user activity. @@ -44,18 +50,21 @@ import org.sleuthkit.datamodel.DataSource; "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program", "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder", "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times", - "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run" -}) + "DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run", + "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header=Domain", + "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header=URL", + "DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header=Last Access",}) public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); private static final int TOP_PROGS_COUNT = 10; - private static final DefaultTableCellRenderer RIGHT_ALIGNED_RENDERER = new DefaultTableCellRenderer(); + private static final int TOP_DOMAINS_COUNT = 10; - static { - RIGHT_ALIGNED_RENDERER.setHorizontalAlignment(JLabel.RIGHT); - } + private final SwingWorkerSequentialRunner loader = new SwingWorkerSequentialRunner(); + private final DataResultJTable topProgramsTable; + private final DataResultJTable recentDomainsTable; + private final List> dataFetchComponents; private DataSource dataSource; @@ -63,8 +72,61 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel { * Creates new form DataSourceUserActivityPanel */ public DataSourceSummaryUserActivityPanel() { + this(new DataSourceTopProgramsSummary(), new DataSourceTopDomainsSummary()); + } + + public DataSourceSummaryUserActivityPanel(DataSourceTopProgramsSummary topProgramsData, DataSourceTopDomainsSummary topDomainsData) { + // set up recent programs table + this.topProgramsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList( + new DefaultColumnModel( + (prog) -> new DefaultCellModel(prog.getProgramName()) + .setTooltip(prog.getProgramPath()), + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header()) + .setWidth(250), + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(), + (prog) -> topProgramsData.getShortFolderName(prog.getProgramPath(), prog.getProgramName())) + .setWidth(150), + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(), + (prog) -> prog.getRunTimes() == null ? "" : Long.toString(prog.getRunTimes())) + .setWidth(80) + .setCellHorizontalAlignment(HorizontalAlign.RIGHT), + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header(), + (prog) -> prog.getLastRun() == null ? "" : DATETIME_FORMAT.format(prog.getLastRun())) + .setWidth(150) + .setCellHorizontalAlignment(HorizontalAlign.RIGHT) + ))); + + // set up recent domains table + recentDomainsTable = new DataResultJTable<>(new DefaultPojoListTableDataModel<>(Arrays.asList( + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_domain_header(), + (d) -> d.getDomain()) + .setWidth(250), + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_url_header(), + (d) -> d.getUrl()) + .setWidth(250), + new DefaultColumnModel( + Bundle.DataSourceSummaryUserActivityPanel_TopDomainsTableModel_lastAccess_header(), + (d) -> DATETIME_FORMAT.format(d.getLastVisit())) + .setWidth(150) + .setCellHorizontalAlignment(HorizontalAlign.RIGHT) + ))); + + // set up acquisition methods + dataFetchComponents = Arrays.asList( + new DataFetchComponents>( + (dataSource) -> topProgramsData.getTopPrograms(dataSource, TOP_PROGS_COUNT), + topProgramsTable::setResult), + new DataFetchComponents>( + (dataSource) -> topDomainsData.getRecentDomains(dataSource, TOP_DOMAINS_COUNT), + recentDomainsTable::setResult) + ); + initComponents(); - topProgramsTable.getTableHeader().setReorderingAllowed(false); } /** @@ -81,170 +143,25 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel { * * @param dataSource The datasource to use in this panel. */ - public void setDataSource(DataSource dataSource) { + public void setDataSource(final DataSource dataSource) { this.dataSource = dataSource; if (dataSource == null || !Case.isCaseOpen()) { - updateTopPrograms(new TopProgramsModel(null)); + dataFetchComponents.forEach((item) -> item.getResultHandler() + .accept(DataLoadingResult.getLoaded(null))); + } else { - updateTopPrograms(getTopProgramsModel(dataSource)); + dataFetchComponents.forEach((item) -> item.getResultHandler() + .accept(DataLoadingResult.getLoading())); + + List> workers = dataFetchComponents + .stream() + .map((components) -> new DataFetchWorker<>(components, dataSource)) + .collect(Collectors.toList()); + + loader.resetLoad(workers); } } - /** - * Updates the Top Programs Table in the gui. - * - * @param data The data in Object[][] form to be used by the - * DefaultTableModel. - */ - private void updateTopPrograms(TopProgramsModel model) { - topProgramsTable.setModel(model); - topProgramsTable.getColumnModel().getColumn(0).setPreferredWidth(250); - topProgramsTable.getColumnModel().getColumn(0).setCellRenderer(PATH_CELL_RENDERER); - topProgramsTable.getColumnModel().getColumn(1).setPreferredWidth(150); - topProgramsTable.getColumnModel().getColumn(2).setCellRenderer(RIGHT_ALIGNED_RENDERER); - topProgramsTable.getColumnModel().getColumn(2).setPreferredWidth(80); - topProgramsTable.getColumnModel().getColumn(3).setPreferredWidth(150); - topProgramsScrollPane.getVerticalScrollBar().setValue(0); - this.repaint(); - } - - /** - * The counts of top programs run. - * - * @param selectedDataSource The DataSource. - * - * @return The JTable data model of counts of program runs. - */ - private static TopProgramsModel getTopProgramsModel(DataSource selectedDataSource) { - List topProgramList - = DataSourceTopProgramsSummary.getTopPrograms(selectedDataSource, TOP_PROGS_COUNT); - - if (topProgramList == null) { - return new TopProgramsModel(null); - } else { - return new TopProgramsModel(topProgramList); - } - } - - /** - * A POJO defining the values present in the name cell. Defines the name as - * well as the path for the tooltip. - */ - private static class ProgramNameCellValue { - - private final String programName; - private final String programPath; - - ProgramNameCellValue(String programName, String programPath) { - this.programName = programName; - this.programPath = programPath; - } - - @Override - public String toString() { - // override so that the value in the cell reads as programName - return programName; - } - - /** - * @return The program name. - */ - String getProgramName() { - return programName; - } - - /** - * @return The path of the program. - */ - String getProgramPath() { - return programPath; - } - } - - /** - * Defines a cell renderer for the first cell rendering the name as the text - * and path as the tooltip. - */ - private static TableCellRenderer PATH_CELL_RENDERER = new DefaultTableCellRenderer() { - - public Component getTableCellRendererComponent( - JTable table, Object value, - boolean isSelected, boolean hasFocus, - int row, int column) { - JLabel c = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - if (value instanceof ProgramNameCellValue) { - ProgramNameCellValue cellValue = (ProgramNameCellValue) value; - c.setToolTipText(cellValue.getProgramPath()); - } - return c; - } - }; - - /** - * Defines the table model for a JTable of the programs. Accepts a list of - * TopProgramsResult objects as rows data source. - */ - private static class TopProgramsModel extends AbstractTableModel { - - private static final long serialVersionUID = 1L; - - // column headers for artifact counts table - private static final String[] TOP_PROGS_COLUMN_HEADERS = new String[]{ - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(), - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(), - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(), - Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header() - }; - - private final List programResults; - - /** - * Main constructor. - * - * @param programResults The results to display. - */ - TopProgramsModel(List programResults) { - this.programResults = programResults == null ? new ArrayList<>() : Collections.unmodifiableList(programResults); - } - - @Override - public String getColumnName(int column) { - return column < 0 || column >= TOP_PROGS_COLUMN_HEADERS.length ? null : TOP_PROGS_COLUMN_HEADERS[column]; - } - - @Override - public int getRowCount() { - return programResults.size(); - } - - @Override - public int getColumnCount() { - return TOP_PROGS_COLUMN_HEADERS.length; - } - - @Override - public Object getValueAt(int rowIndex, int columnIndex) { - if (rowIndex < 0 || rowIndex >= programResults.size()) { - return null; - } - - TopProgramsResult result = programResults.get(rowIndex); - switch (columnIndex) { - case 0: - return new ProgramNameCellValue(result.getProgramName(), result.getProgramPath()); - case 1: - return DataSourceTopProgramsSummary.getShortFolderName(result.getProgramPath(), result.getProgramName()); - case 2: - return result.getRunTimes(); - case 3: - return result.getLastRun() == null ? null : DATETIME_FORMAT.format(result.getLastRun()); - default: - return null; - } - } - - } - /** * 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 @@ -254,40 +171,57 @@ public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel { // //GEN-BEGIN:initComponents private void initComponents() { + javax.swing.JScrollPane contentScrollPane = new javax.swing.JScrollPane(); + javax.swing.JPanel contentPanel = new javax.swing.JPanel(); javax.swing.JLabel programsRunLabel = new javax.swing.JLabel(); - topProgramsScrollPane = new javax.swing.JScrollPane(); - topProgramsTable = new javax.swing.JTable(); + javax.swing.Box.Filler filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel topProgramsTablePanel = topProgramsTable; + javax.swing.Box.Filler filler3 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20), new java.awt.Dimension(0, 20)); + javax.swing.JLabel recentDomainsLabel = new javax.swing.JLabel(); + javax.swing.Box.Filler filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2), new java.awt.Dimension(0, 2)); + javax.swing.JPanel recentDomainsTablePanel = recentDomainsTable; + setMaximumSize(null); + setLayout(new java.awt.BorderLayout()); + + contentScrollPane.setMaximumSize(null); + contentScrollPane.setMinimumSize(null); + + contentPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); + contentPanel.setMaximumSize(new java.awt.Dimension(720, 450)); + contentPanel.setMinimumSize(new java.awt.Dimension(720, 450)); + contentPanel.setLayout(new javax.swing.BoxLayout(contentPanel, javax.swing.BoxLayout.PAGE_AXIS)); + + programsRunLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N + programsRunLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + contentPanel.add(programsRunLabel); + contentPanel.add(filler1); - topProgramsScrollPane.setPreferredSize(new java.awt.Dimension(750, 187)); - topProgramsScrollPane.setViewportView(topProgramsTable); + topProgramsTablePanel.setAlignmentX(0.0F); + topProgramsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187)); + topProgramsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187)); + topProgramsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187)); + contentPanel.add(topProgramsTablePanel); + contentPanel.add(filler3); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(programsRunLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 460, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(128, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(programsRunLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); + recentDomainsLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(recentDomainsLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.recentDomainsLabel.text")); // NOI18N + contentPanel.add(recentDomainsLabel); + contentPanel.add(filler2); + + recentDomainsTablePanel.setAlignmentX(0.0F); + recentDomainsTablePanel.setMaximumSize(new java.awt.Dimension(700, 187)); + recentDomainsTablePanel.setMinimumSize(new java.awt.Dimension(700, 187)); + recentDomainsTablePanel.setPreferredSize(new java.awt.Dimension(700, 187)); + contentPanel.add(recentDomainsTablePanel); + + contentScrollPane.setViewportView(contentPanel); + + add(contentScrollPane, java.awt.BorderLayout.CENTER); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JScrollPane topProgramsScrollPane; - private javax.swing.JTable topProgramsTable; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/internal/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/guiutils/internal/Bundle.properties-MERGED new file mode 100644 index 0000000000..1c109cf5c4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/guiutils/internal/Bundle.properties-MERGED @@ -0,0 +1,2 @@ +DataResultJTable_errorMessage_defaultText=There was an error loading results. +DataResultJTable_loadingMessage_defaultText=Loading results... diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataFetchWorker.java b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataFetchWorker.java index 9fa7288daa..a673e8d265 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataFetchWorker.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataFetchWorker.java @@ -121,24 +121,35 @@ public class DataFetchWorker extends SwingWorker { @Override protected void done() { + // if cancelled, simply return + if (Thread.interrupted() || isCancelled()) { + return; + } + R result = null; try { result = get(); } catch (InterruptedException ignored) { - // if cancelled, set not loaded andt return - resultHandler.accept(DataLoadingResult.getNotLoaded()); + // if cancelled, simply return return; } catch (ExecutionException ex) { - logger.log(Level.WARNING, "There was an error while fetching results.", ex); Throwable inner = ex.getCause(); + // if cancelled during operation, simply return + if (inner != null && inner instanceof InterruptedException) { + return; + } + + // otherwise, there is an error to log + logger.log(Level.WARNING, "There was an error while fetching results.", ex); + if (inner != null && inner instanceof DataProcessorException) { resultHandler.accept(DataLoadingResult.getLoadError((DataProcessorException) inner)); } return; } + // if cancelled, simply return if (Thread.interrupted() || isCancelled()) { - resultHandler.accept(DataLoadingResult.getNotLoaded()); return; } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataLoadingResult.java b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataLoadingResult.java index f341b374a3..d2b1aba199 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataLoadingResult.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DataLoadingResult.java @@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.guiutils.internal; /** * The intermediate or end result of a loading process. */ -class DataLoadingResult { +public class DataLoadingResult { // The state of loading in the result. public enum ProcessorState { diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DefaultPojoListTableDataModel.java b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DefaultPojoListTableDataModel.java index 8671877459..b33fef986d 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/internal/DefaultPojoListTableDataModel.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/internal/DefaultPojoListTableDataModel.java @@ -243,7 +243,7 @@ public class DefaultPojoListTableDataModel extends AbstractTableModel impleme TableColumn col = new TableColumn(i); ColumnModel model = columns.get(i); if (model.getWidth() != null && model.getWidth() >= 0) { - col.setWidth(model.getWidth()); + col.setPreferredWidth(model.getWidth()); } col.setHeaderValue(model.getTitle()); @@ -278,11 +278,15 @@ public class DefaultPojoListTableDataModel extends AbstractTableModel impleme String text = cellModel.getText(); if (StringUtils.isNotBlank(text)) { defaultCell.setText(text); + } else { + defaultCell.setText(null); } String tooltip = cellModel.getTooltip(); if (StringUtils.isNotBlank(tooltip)) { defaultCell.setToolTipText(tooltip); + } else { + defaultCell.setToolTipText(null); } if (columnModel.getCellHorizontalAlignment() != null) { @@ -297,6 +301,8 @@ public class DefaultPojoListTableDataModel extends AbstractTableModel impleme defaultCell.setHorizontalAlignment(JLabel.RIGHT); break; } + } else { + defaultCell.setHorizontalAlignment(JLabel.LEFT); } return defaultCell;