diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form index 153f81561f..58ef6f79fb 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.form @@ -36,7 +36,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java index 018def8ad3..9696e36fa2 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/MultiUserCasesPanel.java @@ -30,11 +30,13 @@ import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.SortOrder; +import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.MultiUserCaseManager.MultiUserCase; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coreutils.Logger; @@ -46,12 +48,12 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; /** * A panel that allows a user to open cases created by auto ingest. */ +@NbBundle.Messages({"MultiUSerCasesPanel.caseListLoading.message=Retrieving Case List"}) final class MultiUserCasesPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(MultiUserCasesPanel.class.getName()); private static final String LOG_FILE_NAME = "auto_ingest_log.txt"; - private static final MultiUserCaseManager.MultiUserCase.LastAccessedDateDescendingComparator REVERSE_DATE_MODIFIED_COMPARATOR = new MultiUserCaseManager.MultiUserCase.LastAccessedDateDescendingComparator(); private static final int CASE_COL_MIN_WIDTH = 30; private static final int CASE_COL_MAX_WIDTH = 2000; private static final int CASE_COL_PREFERRED_WIDTH = 300; @@ -61,6 +63,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { private static final int STATUS_COL_MIN_WIDTH = 55; private static final int STATUS_COL_MAX_WIDTH = 250; private static final int STATUS_COL_PREFERRED_WIDTH = 60; + private static final String CASES_POPULATING_MESSAGE = NbBundle.getMessage(MultiUserCasesPanel.class, "MultiUSerCasesPanel.caseListLoading.message"); /* * The JTable table model for the cases table presented by this view is @@ -69,13 +72,13 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * TODO (RC): Consider unifying this stuff in an enum as in * AutoIngestDashboard to make it less error prone. */ - private static final String CASE_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CaseHeaderText"); - private static final String CREATEDTIME_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); - private static final String COMPLETEDTIME_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); - private static final String STATUS_ICON_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); - private static final String OUTPUT_FOLDER_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); - private static final String METADATA_FILE_HEADER = org.openide.util.NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.MetadataFileHeaderText"); - + private static final String CASE_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CaseHeaderText"); + private static final String CREATEDTIME_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.CreatedTimeHeaderText"); + private static final String COMPLETEDTIME_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.LastAccessedTimeHeaderText"); + private static final String STATUS_ICON_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.StatusIconHeaderText"); + private static final String OUTPUT_FOLDER_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.OutputFolderHeaderText"); + private static final String METADATA_FILE_HEADER = NbBundle.getMessage(MultiUserCasesPanel.class, "ReviewModeCasePanel.MetadataFileHeaderText"); + enum COLUMN_HEADERS { CASE, @@ -89,6 +92,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { private DefaultTableModel caseTableModel; private Path currentlySelectedCase = null; private JDialog parentDialog; + private LoadTableWorker tableWorker; /** * Constructs a panel that allows a user to open cases created by automated @@ -151,7 +155,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { casesTable.removeColumn(casesTable.getColumn(OUTPUT_FOLDER_HEADER)); casesTable.removeColumn(casesTable.getColumn(METADATA_FILE_HEADER)); casesTable.setRowSorter(new RowSorter<>(caseTableModel)); - + casesTable.getRowSorter().toggleSortOrder(casesTable.getColumn(COMPLETEDTIME_HEADER).getModelIndex()); /* * Listen for row selection changes and set button state for the current * selection. @@ -169,33 +173,11 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * Gets the list of cases known to the review mode cases manager and * refreshes the cases table. */ - void refresh() { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - try { - currentlySelectedCase = getSelectedCase(); - MultiUserCaseManager manager = MultiUserCaseManager.getInstance(); - List cases = manager.getCases(); - cases.sort(REVERSE_DATE_MODIFIED_COMPARATOR); - caseTableModel.setRowCount(0); - long now = new Date().getTime(); - for (MultiUserCase autoIngestCase : cases) { - if (passesTimeFilter(now, autoIngestCase.getLastAccessedDate().getTime())) { - caseTableModel.addRow(new Object[]{ - autoIngestCase.getCaseDisplayName(), - autoIngestCase.getCreationDate(), - autoIngestCase.getLastAccessedDate(), - (MultiUserCaseManager.CaseStatus.OK != autoIngestCase.getStatus()) ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, - autoIngestCase.getCaseDirectoryPath().toString(), - autoIngestCase.getMetadataFileName()}); - } - } - setSelectedCase(currentlySelectedCase); - setButtons(); - } catch (MultiUserCaseManager.MultiUserCaseManagerException | CoordinationService.CoordinationServiceException ex) { - LOGGER.log(Level.SEVERE, "Unexpected exception while refreshing the table.", ex); //NON-NLS - } finally { - setCursor(null); + synchronized void refresh() { + if (tableWorker == null || tableWorker.isDone() || tableWorker.isCancelled()) { + //create a new TableWorker to and execute it in a background thread if one is not currently working + tableWorker = new LoadTableWorker(); + tableWorker.execute(); } } @@ -244,7 +226,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { * in the cases table. */ private void setButtons() { - boolean openEnabled = casesTable.getSelectedRow() >= 0 && casesTable.getSelectedRow() < casesTable.getRowCount(); + boolean openEnabled = casesTable.getRowSelectionAllowed() && casesTable.getSelectedRow() >= 0 && casesTable.getSelectedRow() < casesTable.getRowCount(); bnOpen.setEnabled(openEnabled); Path pathToLog = getSelectedCaseLogFilePath(); @@ -589,7 +571,7 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { }//GEN-LAST:event_bnShowLogActionPerformed private void casesTableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_casesTableMouseClicked - if (evt.getClickCount() == 2) { + if (evt.getClickCount() == 2 && casesTable.getRowSelectionAllowed() && casesTable.getSelectedRow() >= 0 && casesTable.getSelectedRow() < casesTable.getRowCount()) { int modelRow = casesTable.convertRowIndexToModel(casesTable.getSelectedRow()); String caseDirectory = (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.OUTPUTFOLDER.ordinal()); Path caseMetadataFilePath = Paths.get(caseDirectory, (String) caseTableModel.getValueAt(modelRow, COLUMN_HEADERS.METADATA_FILE.ordinal())); @@ -617,4 +599,46 @@ final class MultiUserCasesPanel extends javax.swing.JPanel { private javax.swing.JScrollPane scrollPaneTable; // End of variables declaration//GEN-END:variables + private class LoadTableWorker extends SwingWorker { + + @Override + protected Void doInBackground() throws Exception { + try { + if (caseTableModel.getRowCount() == 0) { + //if there are no rows in the current table model add one to let the user know the list of cases is being retrieved + caseTableModel.setRowCount(0); + casesTable.setRowSelectionAllowed(false); + caseTableModel.addRow(new Object[]{CASES_POPULATING_MESSAGE, null, null, null, "", ""}); + } + currentlySelectedCase = getSelectedCase(); + MultiUserCaseManager manager = MultiUserCaseManager.getInstance(); + List cases = manager.getCases(); + caseTableModel.setRowCount(0); + long now = new Date().getTime(); + for (MultiUserCase autoIngestCase : cases) { + if (passesTimeFilter(now, autoIngestCase.getLastAccessedDate().getTime())) { + caseTableModel.addRow(new Object[]{ + autoIngestCase.getCaseDisplayName(), + autoIngestCase.getCreationDate(), + autoIngestCase.getLastAccessedDate(), + (MultiUserCaseManager.CaseStatus.OK != autoIngestCase.getStatus()) ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, + autoIngestCase.getCaseDirectoryPath().toString(), + autoIngestCase.getMetadataFileName()}); + } + } + } catch (MultiUserCaseManager.MultiUserCaseManagerException | CoordinationService.CoordinationServiceException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while refreshing the table.", ex); //NON-NLS + } finally { + //ensure the cases are able to be selected + casesTable.setRowSelectionAllowed(true); + } + return null; + } + + @Override + protected void done() { + setSelectedCase(currentlySelectedCase); + setButtons(); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/LongDateCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/LongDateCellRenderer.java index 373e4e2501..d5fb695425 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/LongDateCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/LongDateCellRenderer.java @@ -43,6 +43,9 @@ public class LongDateCellRenderer extends GrayableCellRenderer { if (value != null) { setText(dateFormat.format(value)); } + else { + setText(""); + } grayCellIfTableNotEnabled(table, isSelected); return this; } diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/StatusIconCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/StatusIconCellRenderer.java index b7ee58e4d4..a22b7975b9 100755 --- a/Core/src/org/sleuthkit/autopsy/guiutils/StatusIconCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/StatusIconCellRenderer.java @@ -61,6 +61,9 @@ public class StatusIconCellRenderer extends GrayableCellRenderer { break; } } + else { + setText(""); + } grayCellIfTableNotEnabled(table, isSelected); return this;