diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 19d70deb62..6f2e6f0816 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.featureaccess.FeatureAccessUtils; import com.google.common.annotations.Beta; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.awt.Cursor; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData; import java.awt.Frame; import java.awt.event.ActionEvent; @@ -92,6 +93,7 @@ import org.sleuthkit.autopsy.casemodule.events.HostsRemovedFromPersonEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountsAddedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountsUpdatedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountsDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAcctInstancesAddedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsUpdatedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsDeletedEvent; @@ -447,6 +449,10 @@ public class Case { * One or more OS accounts have been deleted from the case. */ OS_ACCOUNTS_DELETED, + /** + * One or more OS account instances have been added to the case. + */ + OS_ACCT_INSTANCES_ADDED, /** * One or more hosts have been added to the case. */ @@ -534,6 +540,11 @@ public class Case { eventPublisher.publish(new OsAccountsDeletedEvent(event.getOsAccountObjectIds())); } + @Subscribe + public void publishOsAccountInstancesAddedEvent(TskEvent.OsAcctInstancesAddedTskEvent event) { + eventPublisher.publish(new OsAcctInstancesAddedEvent(event.getOsAccountInstances())); + } + /** * Publishes an autopsy event from the sleuthkit HostAddedEvent * indicating that hosts have been created. @@ -1324,12 +1335,14 @@ public class Case { * opened via the DirectoryTreeTopComponent 'propertyChange()' * method on a DATA_SOURCE_ADDED event. */ + mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (hasData) { CoreComponentControl.openCoreWindows(); } else { //ensure that the DirectoryTreeTopComponent is open so that it's listener can open the core windows including making it visible. DirectoryTreeTopComponent.findInstance(); } + mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); /* * Reset the main window title to: diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java new file mode 100755 index 0000000000..17b8f31e9a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAcctInstancesAddedEvent.java @@ -0,0 +1,64 @@ +/* + * 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.casemodule.events; + +import java.util.ArrayList; +import java.util.List; +import static org.sleuthkit.autopsy.casemodule.Case.Events.OS_ACCT_INSTANCES_ADDED; +import org.sleuthkit.datamodel.OsAccountInstance; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An application event published when OS accounts are added to the Sleuth Kit + * data model for a case. + */ +public final class OsAcctInstancesAddedEvent extends TskDataModelChangedEvent { + + private static final long serialVersionUID = 1L; + + /** + * Constructs an application event published when OS account instances are added to + * the Sleuth Kit data model for a case. + * + * @param osAcctInstances The OS account instances that were added. + */ + public OsAcctInstancesAddedEvent(List osAcctInstances) { + super(OS_ACCT_INSTANCES_ADDED.toString(), null, null, osAcctInstances, OsAccountInstance::getInstanceId); + } + + /** + * Gets the OS accounts that have been added or updated. + * + * @return The OS accounts. + */ + public List getOsAccountInstances() { + return getNewValue(); + } + + @Override + protected List getNewValueObjects(SleuthkitCase caseDb, List ids) throws TskCoreException { + List osAccountInstances = new ArrayList<>(); + for (Long id : ids) { + //RJCTODO + } + return osAccountInstances; + } + +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form index 237bfe4ce8..13284ecec9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.form @@ -34,8 +34,6 @@ - - diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java index c4d0bf0e0a..81fac1ebd6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/contentviewer/OtherOccurrencesPanel.java @@ -35,6 +35,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JMenuItem; @@ -46,7 +49,6 @@ import javax.swing.SwingWorker; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -71,7 +73,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noArtifacts()); private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noResultsFound()); - private static final CorrelationCaseWrapper LOADING_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_loadingResults()); private static final Logger logger = Logger.getLogger(OtherOccurrencesPanel.class.getName()); private static final long serialVersionUID = 1L; private final OtherOccurrencesFilesTableModel filesTableModel; @@ -84,6 +85,11 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { private AbstractFile file = null; private SwingWorker worker; + + // Initializing the JFileChooser in a thread to prevent a block on the EDT + // see https://stackoverflow.com/questions/49792375/jfilechooser-is-very-slow-when-using-windows-look-and-feel + private final FutureTask futureFileChooser = new FutureTask<>(JFileChooser::new); + private JFileChooser CSVFileChooser; /** * Creates new form OtherOccurrencesPanel @@ -93,9 +99,11 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { this.casesTableModel = new OtherOccurrencesCasesTableModel(); this.dataSourcesTableModel = new OtherOccurrencesDataSourcesTableModel(); this.correlationAttributes = new ArrayList<>(); - occurrencePanel = new OccurrencePanel(); initComponents(); customizeComponents(); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(futureFileChooser); } private void customizeComponents() { @@ -245,6 +253,18 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { private void saveToCSV() throws NoCurrentCaseException { if (casesTableModel.getRowCount() > 0) { + + if(CSVFileChooser == null) { + try{ + CSVFileChooser = futureFileChooser.get(); + } catch (InterruptedException | ExecutionException ex) { + // If something happened with the thread try and + // initalized the chooser now + logger.log(Level.WARNING, "A failure occurred in the JFileChooser background thread"); + CSVFileChooser = new JFileChooser(); + } + } + Calendar now = Calendar.getInstance(); String fileName = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS_other_data_sources.csv", now); CSVFileChooser.setCurrentDirectory(new File(Case.getCurrentCaseThrows().getExportDirectory())); @@ -258,8 +278,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS } - CSVWorker worker = new CSVWorker(selectedFile, file, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes)); - worker.execute(); + CSVWorker csvWorker = new CSVWorker(selectedFile, file, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes)); + csvWorker.execute(); } } } @@ -685,7 +705,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { exportToCSVMenuItem = new javax.swing.JMenuItem(); showCaseDetailsMenuItem = new javax.swing.JMenuItem(); showCommonalityMenuItem = new javax.swing.JMenuItem(); - CSVFileChooser = new javax.swing.JFileChooser(); tableContainerPanel = new javax.swing.JPanel(); tablesViewerSplitPane = new javax.swing.JSplitPane(); caseDatasourceFileSplitPane = new javax.swing.JSplitPane(); @@ -704,12 +723,12 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); rightClickPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() { - public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { - rightClickPopupMenuPopupMenuWillBecomeVisible(evt); + public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { } public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) { } - public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) { + public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) { + rightClickPopupMenuPopupMenuWillBecomeVisible(evt); } }); @@ -857,7 +876,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JFileChooser CSVFileChooser; private javax.swing.JSplitPane caseDatasourceFileSplitPane; private javax.swing.JSplitPane caseDatasourceSplitPane; private javax.swing.JScrollPane caseScrollPane; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java index 06b0a42fb7..177c397c78 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaViewImagePanel.java @@ -34,6 +34,9 @@ import java.util.Collections; import java.util.List; import static java.util.Objects.nonNull; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; @@ -150,7 +153,7 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final JMenuItem exportTagsMenuItem; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - private final JFileChooser exportChooser; + private JFileChooser exportChooser; @ThreadConfined(type = ThreadConfined.ThreadType.AWT) private final JFXPanel fxPanel; @@ -189,10 +192,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private Task readImageFileTask; private volatile ImageTransforms imageTransforms; - - static { - ImageIO.scanForPlugins(); - } + + // Initializing the JFileChooser in a thread to prevent a block on the EDT + // see https://stackoverflow.com/questions/49792375/jfilechooser-is-very-slow-when-using-windows-look-and-feel + private final FutureTask futureFileChooser = new FutureTask<>(JFileChooser::new); /** * Constructs a media image file viewer implemented as a Swing panel that @@ -210,9 +213,9 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan initComponents(); imageTransforms = new ImageTransforms(0, 0, true); - - exportChooser = new JFileChooser(); - exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle()); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(futureFileChooser); //Build popupMenu when Tags Menu button is pressed. imageTaggingOptions = new JPopupMenu(); @@ -1043,6 +1046,18 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan final AbstractFile file = imageFile; tagsGroup.clearFocus(); SwingUtilities.invokeLater(() -> { + + if(exportChooser == null) { + try { + exportChooser = futureFileChooser.get(); + } catch (InterruptedException | ExecutionException ex) { + // If something happened with the thread try and + // initalized the chooser now + logger.log(Level.WARNING, "A failure occurred in the JFileChooser background thread"); + exportChooser = new JFileChooser(); + } + } + exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); //Always base chooser location to export folder exportChooser.setCurrentDirectory(new File(Case.getCurrentCase().getExportDirectory())); diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 5bd649e8cb..7411377733 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -33,6 +33,7 @@ import java.util.logging.Handler; import java.util.logging.Level; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; +import javax.imageio.ImageIO; import net.sf.sevenzipjbinding.SevenZip; import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.apache.commons.io.FileUtils; @@ -44,6 +45,7 @@ import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.actions.IngestRunningCheck; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.core.UserPreferences.SETTINGS_PROPERTIES; +import org.sleuthkit.autopsy.corelibs.OpenCvLoader; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ModuleSettings; @@ -66,6 +68,13 @@ public class Installer extends ModuleInstall { static { loadDynLibraries(); + + // This call was moved from MediaViewImagePanel so that it is + // not called during top level component construction. + ImageIO.scanForPlugins(); + + // This will cause OpenCvLoader to load its library instead of + OpenCvLoader.openCvIsLoaded(); } private static void loadDynLibraries() { diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index 6ae521de1e..8d9c3a02b9 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -194,7 +194,7 @@ public class ImageUtils { public static SortedSet getSupportedImageMimeTypes() { return Collections.unmodifiableSortedSet(SUPPORTED_IMAGE_MIME_TYPES); } - + /** * Get the default thumbnail, which is the icon for a file. Used when we can * not generate a content based thumbnail. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 1068c72973..e3bad44705 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -80,6 +80,7 @@ import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; import org.sleuthkit.autopsy.texttranslation.TextTranslationService; import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; import org.sleuthkit.datamodel.AnalysisResult; +import org.sleuthkit.datamodel.BlackboardArtifact.Category; import org.sleuthkit.datamodel.Score; /** @@ -109,17 +110,6 @@ public class BlackboardArtifactNode extends AbstractContentNode NODES_TO_EXPAND = Stream.of(AnalysisResults.getName(), DataArtifacts.getName(), ViewsNode.NAME) + .collect(Collectors.toSet()); + /** * the constructor */ @@ -134,31 +136,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // only allow one item to be selected at a time getTree().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); - //Hook into the JTree and pre-expand the Views Node and Results node when a user - //expands an item in the tree that makes these nodes visible. - ((ExpansionBeanTreeView) getTree()).addTreeExpansionListener(new TreeExpansionListener() { - @Override - public void treeExpanded(TreeExpansionEvent event) { - //Bail immediately if we are not in the Group By view. - //Assumption here is that the views are already expanded. - if (!CasePreferences.getGroupItemsInTreeByDataSource()) { - return; - } - Node expandedNode = Visualizer.findNode(event.getPath().getLastPathComponent()); - for (Node child : em.getRootContext().getChildren().getNodes()) { - if (child.equals(expandedNode)) { - preExpandNodes(child.getChildren()); - } - } - } - - @Override - public void treeCollapsed(TreeExpansionEvent event) { - //Do nothing - } - - }); // remove the close button putClientProperty(TopComponent.PROP_CLOSING_DISABLED, Boolean.TRUE); setName(NbBundle.getMessage(DirectoryTreeTopComponent.class, "CTL_DirectoryTreeTopComponent")); @@ -201,30 +179,23 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat */ private void preExpandNodes(Children rootChildren) { BeanTreeView tree = getTree(); - for (String categoryKey : new String[]{AnalysisResults.getName(), DataArtifacts.getName()}) { - Node categoryNode = rootChildren.findChild(categoryKey); - if (!Objects.isNull(categoryNode)) { - tree.expandNode(categoryNode); - Children resultsChildren = categoryNode.getChildren(); - Arrays.stream(resultsChildren.getNodes()).forEach(tree::expandNode); - } - } - - Node views = rootChildren.findChild(ViewsNode.NAME); - if (!Objects.isNull(views)) { - tree.expandNode(views); + // using getNodes(true) to fetch children so that async nodes are loaded + Node[] rootChildrenNodes = rootChildren.getNodes(true); + if (rootChildrenNodes == null || rootChildrenNodes.length < 1) { + return; } // expand all nodes parents of and including hosts if group by host/person if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) { - Node[] rootNodes = rootChildren.getNodes(); - if (rootNodes != null) { - Stream.of(rootNodes) - .flatMap((n) -> getHostNodesAndParents(n).stream()) - .filter((n) -> n != null) - .forEach((n) -> tree.expandNode(n)); - } + Stream.of(rootChildrenNodes) + .flatMap((n) -> getHostNodesAndParents(n).stream()) + .filter((n) -> n != null) + .forEach(tree::expandNode); + } else { + Stream.of(rootChildrenNodes) + .filter(n -> n != null && NODES_TO_EXPAND.contains(n.getName())) + .forEach(tree::expandNode); } }