diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java index 9ab4ec281b..cbc54aadc3 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,66 +21,76 @@ package org.sleuthkit.autopsy.guiutils; import java.awt.Component; import java.time.Duration; import javax.swing.JTable; -import static javax.swing.SwingConstants.CENTER; /** * A JTable cell renderer that renders a duration represented as a long as a * string with days, hours, minutes, and seconds components. It center-aligns * cell content and grays out the cell if the table is disabled. */ -public class DurationCellRenderer extends GrayableCellRenderer { +public final class DurationCellRenderer extends GrayableCellRenderer { private static final long serialVersionUID = 1L; + private static final char UNIT_SEPARATOR_CHAR = ':'; public DurationCellRenderer() { - setHorizontalAlignment(CENTER); + setHorizontalAlignment(LEFT); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value instanceof Long) { - { - setText(DurationCellRenderer.longToDurationString((long) value)); - } + setText(DurationCellRenderer.longToDurationString((long) value)); } grayCellIfTableNotEnabled(table, isSelected); return this; } + public static char getUnitSeperator() { + return UNIT_SEPARATOR_CHAR; + } + /** * Convert a duration represented by a long to a human readable string with * with days, hours, minutes, and seconds components. * - * @param duration - the representation of the duration in long form + * @param duration - The representation of the duration in long form. * - * @return - the representation of the duration in String form. + * @return - The representation of the duration in String form. */ public static String longToDurationString(long duration) { Duration d = Duration.ofMillis(duration); if (d.isNegative()) { - d = Duration.ofMillis(-duration); + d = Duration.ofMillis(0); //it being 0 for a few seconds seems preferable to it counting down to 0 then back up from 0 } - - String result; long days = d.toDays(); long hours = d.minusDays(days).toHours(); long minutes = d.minusDays(days).minusHours(hours).toMinutes(); long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); - - if (minutes > 0) { - if (hours > 0) { - if (days > 0) { - result = days + " d " + hours + " h " + minutes + " m " + seconds + " s"; - } else { - result = hours + " h " + minutes + " m " + seconds + " s"; - } - } else { - result = minutes + " m " + seconds + " s"; - } - } else { - result = seconds + " s"; + if (days < 0) { + days = 0; } - return result; + if (hours < 0) { + hours = 0; + } + if (minutes < 0) { + minutes = 0; + } + if (seconds < 0) { + seconds = 0; + } + StringBuilder results = new StringBuilder(12); + if (days < 99) { + results.append(String.format("%02d", days)); + } else { + results.append(days); //in the off chance something has been running for over 99 days lets allow it to stand out a bit by having as many characters as it needs + } + results.append(UNIT_SEPARATOR_CHAR); + results.append(String.format("%02d", hours)); + results.append(UNIT_SEPARATOR_CHAR); + results.append(String.format("%02d", minutes)); + results.append(UNIT_SEPARATOR_CHAR); + results.append(String.format("%02d", seconds)); + return results.toString(); } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index 656b98b278..fdecd191f3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -143,7 +143,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final String UPDATE_TASKS_THREAD_NAME = "AID-update-tasks-%d"; private static final String LOCAL_HOST_NAME = NetworkUtils.getLocalHostName(); - private static final String RUNNING_AS_SERVICE_PROPERTY = "autoingest.runningasservice"; + private static final String RUNNING_AS_SERVICE_PROPERTY = "autoingest.runningasservice"; private static final Logger sysLogger = AutoIngestSystemLogger.getLogger(); private static AutoIngestControlPanel instance; private final DefaultTableModel pendingTableModel; @@ -160,7 +160,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * Maintain a mapping of each service to it's last status update. */ private final ConcurrentHashMap statusByService; - + /* * The enum is used in conjunction with the DefaultTableModel class to * provide table models for the JTables used to display a view of the @@ -177,7 +177,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { "AutoIngestControlPanel.JobsTableModel.ColumnHeader.StartedTime=Stage Started", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CompletedTime=Job Completed", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Stage=Stage", - "AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime=Time in Stage", + "# {0} - unitSeparator", + "AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime=Time in Stage (dd{0}hh{0}mm{0}ss)", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Status=Status", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CaseFolder=Case Folder", "AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob= Local Job?", @@ -193,7 +194,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { STARTED_TIME(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.StartedTime")), COMPLETED_TIME(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CompletedTime")), STAGE(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Stage")), - STAGE_TIME(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime")), + STAGE_TIME(Bundle.AutoIngestControlPanel_JobsTableModel_ColumnHeader_StageTime(DurationCellRenderer.getUnitSeperator())), STATUS(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.Status")), CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.CaseFolder")), IS_LOCAL_JOB(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.JobsTableModel.ColumnHeader.LocalJob")), @@ -250,7 +251,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * controlling automated ingest for a single node within the cluster. */ private AutoIngestControlPanel() { - + this.statusByService = new ConcurrentHashMap<>(); //Disable the main window so they can only use the dashboard (if we used setVisible the taskBar icon would go away) @@ -290,10 +291,10 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * Update status of the services on the dashboard */ private void displayServicesStatus() { - tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message", - statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message", + statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); String upStatus = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up"); if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 @@ -304,7 +305,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { tbServicesStatusMessage.setForeground(Color.BLACK); } } - + /** * Queries the services monitor and sets the text for the services status * text box. @@ -411,7 +412,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); - + column = pendingTable.getColumn(JobsTableModelColumns.OCR.getColumnHeader()); column.setCellRenderer(new OcrIconCellRenderer()); column.setMaxWidth(OCR_COLUMN_MAX_WIDTH); @@ -469,7 +470,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.OCR.getColumnHeader())); - + /* * Set up a column to display the cases associated with the jobs. */ @@ -566,7 +567,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); - + /* * Set up a column to display the cases associated with the jobs. */ @@ -617,7 +618,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { column.setMaxWidth(STATUS_COL_MAX_WIDTH); column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); column.setWidth(STATUS_COL_PREFERRED_WIDTH); - + /* * Set up a column to display OCR enabled/disabled flag. */ @@ -732,30 +733,30 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { } PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { - + String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); String status = evt.getNewValue().toString(); - + if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { status = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up"); } else if (status.equals(ServicesMonitor.ServiceStatus.DOWN.toString())) { status = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down"); sysLogger.log(Level.SEVERE, "Connection to {0} is down", serviceDisplayName); //NON-NLS } - + // if the status update is for an existing service who's status hasn't changed - do nothing. if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { return; } - + statusByService.put(serviceDisplayName, status); displayServicesStatus(); }; - + // Subscribe to all multi-user services in order to display their status Set servicesList = new HashSet<>(); servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); - servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); + servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); @@ -879,7 +880,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { case JOB_COMPLETED: case CASE_DELETED: case REPROCESS_JOB: - case OCR_STATE_CHANGE: + case OCR_STATE_CHANGE: updateExecutor.submit(new UpdateAllJobsTablesTask()); break; case PAUSED_BY_USER_REQUEST: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 24a1e57fb9..cf982d13a3 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -53,7 +53,8 @@ final class AutoIngestJobsNode extends AbstractNode { "AutoIngestJobsNode.dataSource.text=Data Source", "AutoIngestJobsNode.hostName.text=Host Name", "AutoIngestJobsNode.stage.text=Stage", - "AutoIngestJobsNode.stageTime.text=Time in Stage", + "# {0} - unitSeparator", + "AutoIngestJobsNode.stageTime.text=Time in Stage (dd{0}hh{0}mm{0}ss)", "AutoIngestJobsNode.jobCreated.text=Job Created", "AutoIngestJobsNode.jobCompleted.text=Job Completed", "AutoIngestJobsNode.priority.text=Prioritized", @@ -345,8 +346,10 @@ final class AutoIngestJobsNode extends AbstractNode { jobWrapper.getProcessingHostName())); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), status.getDescription())); - ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), - DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())))); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(DurationCellRenderer.getUnitSeperator()), + Bundle.AutoIngestJobsNode_stageTime_text(DurationCellRenderer.getUnitSeperator()), + Bundle.AutoIngestJobsNode_stageTime_text(DurationCellRenderer.getUnitSeperator()), + DurationCellRenderer.longToDurationString(Date.from(Instant.now()).getTime() - status.getStartDate().getTime()))); break; case COMPLETED_JOB: ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), @@ -356,7 +359,7 @@ final class AutoIngestJobsNode extends AbstractNode { ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), jobWrapper.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(), Bundle.AutoIngestJobsNode_ocr_text(), - jobWrapper.getOcrEnabled())); + jobWrapper.getOcrEnabled())); break; default: } @@ -377,7 +380,7 @@ final class AutoIngestJobsNode extends AbstractNode { PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(jobWrapper.getJob()); deprioritizeCaseAction.setEnabled(jobWrapper.getPriority() > 0); actions.add(deprioritizeCaseAction); - + actions.add(new AutoIngestAdminActions.EnableOCR(jobWrapper.getJob())); AutoIngestAdminActions.DisableOCR disableOCRAction = new AutoIngestAdminActions.DisableOCR(jobWrapper.getJob()); disableOCRAction.setEnabled(jobWrapper.getOcrEnabled() == true); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index d335a35430..12c929d23d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeRefreshEvents.AutoIngestRefreshEvent; +import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; /** @@ -64,6 +65,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa customize(); } + + /** * Set up the AutoIngestJobsPanel's so that its outlineView is displaying * the correct columns for the specified AutoIngestJobStatus @@ -99,7 +102,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), - Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text()); + Bundle.AutoIngestJobsNode_stageTime_text(DurationCellRenderer.getUnitSeperator()), + Bundle.AutoIngestJobsNode_stageTime_text(DurationCellRenderer.getUnitSeperator())); indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); if (indexOfColumn != INVALID_INDEX) { outline.setColumnSorted(indexOfColumn, true, 1); @@ -124,7 +128,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_OCR_WIDTH); outline.getColumnModel().getColumn(indexOfColumn).setCellRenderer(new OcrIconCellRenderer()); - } + } break; default: } @@ -177,8 +181,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * Update the contents of this AutoIngestJobsPanel while retaining currently * selected node. * - * @param refreshEvent - the AutoIngestRefreshEvent which will provide the new - * contents + * @param refreshEvent - the AutoIngestRefreshEvent which will provide the + * new contents */ void refresh(AutoIngestRefreshEvent refreshEvent) { synchronized (this) { @@ -191,7 +195,6 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa } outline.setRowSelectionAllowed(true); outline.setFocusable(true); - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED index 2a1e361537..33de996d54 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties-MERGED @@ -78,7 +78,8 @@ AutoIngestControlPanel.JobsTableModel.ColumnHeader.ManifestFilePath=\ Manifest F AutoIngestControlPanel.JobsTableModel.ColumnHeader.OCR=OCR AutoIngestControlPanel.JobsTableModel.ColumnHeader.Priority=Prioritized AutoIngestControlPanel.JobsTableModel.ColumnHeader.Stage=Stage -AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime=Time in Stage +# {0} - unitSeparator +AutoIngestControlPanel.JobsTableModel.ColumnHeader.StageTime=Time in Stage (dd{0}hh{0}mm{0}ss) AutoIngestControlPanel.JobsTableModel.ColumnHeader.StartedTime=Stage Started AutoIngestControlPanel.JobsTableModel.ColumnHeader.Status=Status AutoIngestControlPanel.OK=OK @@ -140,7 +141,8 @@ AutoIngestJobsNode.prioritized.false=No AutoIngestJobsNode.prioritized.true=Yes AutoIngestJobsNode.priority.text=Prioritized AutoIngestJobsNode.stage.text=Stage -AutoIngestJobsNode.stageTime.text=Time in Stage +# {0} - unitSeparator +AutoIngestJobsNode.stageTime.text=Time in Stage (dd{0}hh{0}mm{0}ss) AutoIngestJobsNode.status.text=Status AutoIngestJobsPanel.waitNode.text=Please Wait... AutoIngestMetricsDialog.initReportText=Select a date above and click the 'Generate Metrics Report' button to generate\na metrics report.