diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java new file mode 100644 index 0000000000..86c731e78b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/datamodel/IngestModuleCheckUtil.java @@ -0,0 +1,146 @@ +/* + * 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.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.IngestJobInfo; +import org.sleuthkit.datamodel.IngestModuleInfo; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Utilities for checking if an ingest module has been run on a datasource. + */ +@Messages({ + "IngestModuleCheckUtil_recentActivityModuleName=Recent Activity", + +}) +public class IngestModuleCheckUtil { + public static final String RECENT_ACTIVITY_FACTORY = "org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory"; + public static final String RECENT_ACTIVITY_MODULE_NAME = Bundle.IngestModuleCheckUtil_recentActivityModuleName(); + + // IngestModuleInfo separator for unique_name + private static final String UNIQUE_NAME_SEPARATOR = "-"; + + private final SleuthkitCaseProvider caseProvider; + + /** + * Main constructor. + */ + public IngestModuleCheckUtil() { + this(SleuthkitCaseProvider.DEFAULT); + + } + + /** + * Main constructor with external dependencies specified. This constructor + * is designed with unit testing in mind since mocked dependencies can be + * utilized. + * + * @param provider The object providing the current SleuthkitCase. + * @param logger The logger to use. + */ + public IngestModuleCheckUtil(SleuthkitCaseProvider provider) { + + this.caseProvider = provider; + } + + + /** + * Gets the fully qualified factory from the IngestModuleInfo. + * @param info The IngestJobInfo. + * @return The fully qualified factory. + */ + private static String getFullyQualifiedFactory(IngestModuleInfo info) { + if (info == null) { + return null; + } + + String qualifiedName = info.getUniqueName(); + if (StringUtils.isBlank(qualifiedName)) { + return null; + } + + return qualifiedName.split(UNIQUE_NAME_SEPARATOR)[0]; + } + + + /** + * Whether or not the ingest job info contains the ingest modulename. + * @param info The IngestJobInfo. + * @param fullyQualifiedFactory The fully qualified classname of the relevant factory. + * @return True if the ingest module name is contained in the data. + */ + private static boolean hasIngestModule(IngestJobInfo info, String fullyQualifiedFactory) { + if (info == null || info.getIngestModuleInfo() == null || StringUtils.isBlank(fullyQualifiedFactory)) { + return false; + } + + return info.getIngestModuleInfo().stream() + .anyMatch((moduleInfo) -> { + String thisQualifiedFactory = getFullyQualifiedFactory(moduleInfo); + return fullyQualifiedFactory.equalsIgnoreCase(thisQualifiedFactory); + }); + } + + /** + * Whether or not a data source has been ingested with a particular ingest module. + * @param dataSource The datasource. + * @param fullyQualifiedFactory The fully qualified classname of the relevant factory. + * @return Whether or not a data source has been ingested with a particular ingest module. + * @throws TskCoreException + * @throws SleuthkitCaseProviderException + */ + public boolean isModuleIngested(DataSource dataSource, String fullyQualifiedFactory) + throws TskCoreException, SleuthkitCaseProviderException { + if (dataSource == null) { + return false; + } + + long dataSourceId = dataSource.getId(); + + return caseProvider.get().getIngestJobs().stream() + .anyMatch((ingestJob) -> { + return ingestJob != null + && ingestJob.getObjectId() == dataSourceId + && hasIngestModule(ingestJob, fullyQualifiedFactory); + }); + + } + + /** + * Get a mapping of fully qualified factory name to display name. + * @param skCase The SleuthkitCase. + * @return The mapping of fully qualified factory name to display name. + * @throws TskCoreException + */ + public static Map getFactoryDisplayNames(SleuthkitCase skCase) throws TskCoreException { + return skCase.getIngestJobs().stream() + .flatMap(ingestJob -> ingestJob.getIngestModuleInfo().stream()) + .collect(Collectors.toMap( + (moduleInfo) -> getFullyQualifiedFactory(moduleInfo), + (moduleInfo) -> moduleInfo.getDisplayName(), + (a,b) -> a)); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java new file mode 100644 index 0000000000..044c9c0346 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourcesummary/uiutils/IngestRunningLabel.java @@ -0,0 +1,162 @@ +/* + * 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.uiutils; + +import java.awt.BorderLayout; +import java.beans.PropertyChangeListener; +import java.net.URL; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.ingest.IngestManager; + +/** + * JLabel that shows ingest is running. + */ +@Messages({ + "IngestRunningLabel_defaultMessage=Ingest is currently running." +}) +public class IngestRunningLabel extends JPanel { + + public static final String DEFAULT_MESSAGE = Bundle.IngestRunningLabel_defaultMessage(); + private static final URL DEFAULT_ICON = IngestRunningLabel.class.getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png"); + + private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of( + IngestManager.IngestJobEvent.STARTED, + IngestManager.IngestJobEvent.CANCELLED, + IngestManager.IngestJobEvent.COMPLETED + ); + + private static Set activeLabels = new HashSet<>(); + private static PropertyChangeListener classListener = null; + private static Object lockObject = new Object(); + + /** + * Setup ingest event listener for the current label. + * + * @param label The label. + */ + private static void setupListener(IngestRunningLabel label) { + synchronized (lockObject) { + + // if listener is not initialized, initialize it. + if (classListener == null) { + classListener = (evt) -> { + + if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.STARTED.toString())) { + // ingest started + notifyListeners(true); + + } else if (evt.getPropertyName().equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || evt.getPropertyName().equals(IngestManager.IngestJobEvent.COMPLETED.toString())) { + // ingest cancelled or finished + notifyListeners(false); + + } + }; + IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, classListener); + } + + // add the item to the set + activeLabels.add(label); + } + } + + /** + * Notifies all listening instances of an update in ingest state. + * + * @param ingestIsRunning Whether or not ingest is running currently. + */ + private static void notifyListeners(boolean ingestIsRunning) { + synchronized (lockObject) { + for (IngestRunningLabel label : activeLabels) { + label.refreshState(ingestIsRunning); + } + } + } + + /** + * Removes a label from listening events. + * + * @param label The label to remove from listening events. + */ + private static void removeListener(IngestRunningLabel label) { + synchronized (lockObject) { + activeLabels.remove(label); + if (activeLabels.isEmpty() && classListener != null) { + IngestManager.getInstance().removeIngestJobEventListener(classListener); + classListener = null; + } + } + } + + /** + * Main constructor with default message and showing icon. + */ + public IngestRunningLabel() { + this(DEFAULT_MESSAGE, true); + } + + /** + * Constructor. + * + * @param message The message to be shown. + * @param showWarningIcon Whether or not to show warning icon. + */ + public IngestRunningLabel(String message, boolean showWarningIcon) { + JLabel jlabel = new JLabel(); + jlabel.setText(message); + if (showWarningIcon) { + jlabel.setIcon(new ImageIcon(DEFAULT_ICON)); + } + + setLayout(new BorderLayout()); + add(jlabel, BorderLayout.CENTER); + + setupListener(this); + refreshState(); + } + + /** + * Refresh state of this label based on ingest status. + */ + protected final void refreshState() { + refreshState(IngestManager.getInstance().isIngestRunning()); + } + + /** + * Refresh state of this label based on ingest status. + * + * @param ingestIsRunning True if ingest is running. + */ + protected final void refreshState(boolean ingestIsRunning) { + setVisible(ingestIsRunning); + } + + /** + * Unregister this instance from listening for ingest status changes. + */ + public void unregister() { + removeListener(this); + } +}