From 1c8f9edb4e568cebc1d41f2a497a59fd026bfaf3 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 9 Aug 2016 16:40:13 -0400 Subject: [PATCH] First cut at moving options panel into Autopsy --- .../configuration/AdvancedOptionsPanel.form | 300 ++ .../configuration/AdvancedOptionsPanel.java | 336 +++ .../autopsy/configuration/Bundle.properties | 146 + .../configuration/OpenOptionsPanelAction.java | 66 + .../autopsy/configuration/OptionsDialog.java | 84 + .../autopsy/configuration/OptionsPanel.form | 1237 +++++++++ .../autopsy/configuration/OptionsPanel.java | 2434 +++++++++++++++++ .../configuration/SharedConfiguration.java | 1231 +++++++++ 8 files changed, 5834 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/OpenOptionsPanelAction.java create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/OptionsDialog.java create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.form create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.java create mode 100644 Core/src/org/sleuthkit/autopsy/configuration/SharedConfiguration.java diff --git a/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.form new file mode 100644 index 0000000000..c9b4d64896 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.form @@ -0,0 +1,300 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.java new file mode 100644 index 0000000000..81ae74d135 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/AdvancedOptionsPanel.java @@ -0,0 +1,336 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.configuration; + +import java.util.ArrayList; +import java.util.Collection; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComponent; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.UserPreferences; + +class AdvancedOptionsPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; + + AdvancedOptionsPanel(OptionsPanel.OptionsUiMode mode) { + initComponents(); + tbWarning.setLineWrap(true); + tbWarning.setWrapStyleWord(true); + load(mode); + } + + private void initThreadCount() { + int availableProcessors = Runtime.getRuntime().availableProcessors(); + Integer fileIngestThreadCountChoices[]; + if (availableProcessors >= 16) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8, 12, 16}; + } else if (availableProcessors >= 12 && availableProcessors <= 15) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8, 12}; + } else if (availableProcessors >= 8 && availableProcessors <= 11) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6, 8}; + } else if (availableProcessors >= 6 && availableProcessors <= 7) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4, 6}; + } else if (availableProcessors >= 4 && availableProcessors <= 5) { + fileIngestThreadCountChoices = new Integer[]{1, 2, 4}; + } else if (availableProcessors >= 2 && availableProcessors <= 3) { + fileIngestThreadCountChoices = new Integer[]{1, 2}; + } else { + fileIngestThreadCountChoices = new Integer[]{1}; + } + numberOfFileIngestThreadsComboBox.setModel(new DefaultComboBoxModel<>(fileIngestThreadCountChoices)); + numberOfFileIngestThreadsComboBox.setSelectedItem(UserPreferences.numberOfFileIngestThreads()); + } + + private void load(OptionsPanel.OptionsUiMode mode) { + initThreadCount(); + spSecondsBetweenJobs.setValue(UserPreferences.getSecondsToSleepBetweenCases()); + spMaximumRetryAttempts.setValue(UserPreferences.getMaxNumTimesToProcessImage()); + int maxJobsPerCase = UserPreferences.getMaxConcurrentJobsForOneCase(); + spConcurrentJobsPerCase.setValue(maxJobsPerCase); + spSecondsBetweenJobs.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + spMaximumRetryAttempts.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + cbTimeoutEnabled.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + lbSecondsBetweenJobs.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + lbTimeoutText.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + lbRetriesAllowed.setEnabled(mode == OptionsPanel.OptionsUiMode.UTILITY || mode == OptionsPanel.OptionsUiMode.AIM); + cbTimeoutEnabled.setSelected(UserPreferences.getIsTimeOutEnabled()); + int timeOutHrs = UserPreferences.getProcessTimeOutHrs(); + spTimeoutHours.setValue(timeOutHrs); + setCheckboxEnabledState(); + + Collection uiComponents = new ArrayList<>(); + uiComponents.add(cbTimeoutEnabled); + uiComponents.add(spSecondsBetweenJobs); + uiComponents.add(spMaximumRetryAttempts); + uiComponents.add(spSecondsBetweenJobs); + uiComponents.add(lbSecondsBetweenJobs); + uiComponents.add(lbTimeoutText); + uiComponents.add(lbRetriesAllowed); + uiComponents.add(lbNumberOfThreads); + uiComponents.add(numberOfFileIngestThreadsComboBox); + uiComponents.add(lbRestartRequired); + uiComponents.add(lbTimeoutHours); + uiComponents.add(spTimeoutHours); + uiComponents.add(spConcurrentJobsPerCase); + + String disabledText = " " + NbBundle.getMessage(OptionsPanel.class, "AdvancedOptionsPanel.ItemDisabled.text"); + for (JComponent item : uiComponents) { + if (!item.isEnabled()) { + item.setToolTipText(item.getToolTipText() + disabledText); + } + } + } + + void store() { + UserPreferences.setSecondsToSleepBetweenCases((int) spSecondsBetweenJobs.getValue()); + UserPreferences.setMaxNumTimesToProcessImage((int) spMaximumRetryAttempts.getValue()); + UserPreferences.setMaxConcurrentIngestNodesForOneCase((int) spConcurrentJobsPerCase.getValue()); + UserPreferences.setNumberOfFileIngestThreads((Integer) numberOfFileIngestThreadsComboBox.getSelectedItem()); + boolean isChecked = cbTimeoutEnabled.isSelected(); + UserPreferences.setIsTimeOutEnabled(isChecked); + if (isChecked) { + // only store time out if it is enabled + int timeOutHrs = (int) spTimeoutHours.getValue(); + UserPreferences.setProcessTimeOutHrs(timeOutHrs); + } + } + + private void setCheckboxEnabledState() { + // Enable the timeout edit box iff the checkbox is checked and enabled + if (cbTimeoutEnabled.isEnabled() && cbTimeoutEnabled.isSelected()) { + spTimeoutHours.setEnabled(true); + lbTimeoutHours.setEnabled(true); + } else { + spTimeoutHours.setEnabled(false); + lbTimeoutHours.setEnabled(false); + } + } + + /** + * 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 + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lbSecondsBetweenJobs = new javax.swing.JLabel(); + spSecondsBetweenJobs = new javax.swing.JSpinner(); + lbNumberOfThreads = new javax.swing.JLabel(); + numberOfFileIngestThreadsComboBox = new javax.swing.JComboBox<>(); + lbRestartRequired = new javax.swing.JLabel(); + lbRetriesAllowed = new javax.swing.JLabel(); + spMainScrollPane = new javax.swing.JScrollPane(); + tbWarning = new javax.swing.JTextArea(); + lbTimeoutText = new javax.swing.JLabel(); + lbTimeoutHours = new javax.swing.JLabel(); + cbTimeoutEnabled = new javax.swing.JCheckBox(); + filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(130, 0), new java.awt.Dimension(130, 0), new java.awt.Dimension(130, 32767)); + spMaximumRetryAttempts = new javax.swing.JSpinner(); + lbConcurrentJobsPerCase = new javax.swing.JLabel(); + spConcurrentJobsPerCase = new javax.swing.JSpinner(); + spTimeoutHours = new javax.swing.JSpinner(); + lbSecondsBetweenJobsSeconds = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(lbSecondsBetweenJobs, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbSecondsBetweenJobs.text")); // NOI18N + lbSecondsBetweenJobs.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbSecondsBetweenJobs.toolTipText")); // NOI18N + + spSecondsBetweenJobs.setModel(new javax.swing.SpinnerNumberModel(30, 30, 3600, 10)); + spSecondsBetweenJobs.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.spSecondsBetweenJobs.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(lbNumberOfThreads, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbNumberOfThreads.text")); // NOI18N + + numberOfFileIngestThreadsComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + numberOfFileIngestThreadsComboBoxActionPerformed(evt); + } + }); + + lbRestartRequired.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(lbRestartRequired, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbRestartRequired.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(lbRetriesAllowed, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbRetriesAllowed.text")); // NOI18N + lbRetriesAllowed.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbRetriesAllowed.toolTipText")); // NOI18N + lbRetriesAllowed.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); + + tbWarning.setEditable(false); + tbWarning.setColumns(20); + tbWarning.setFont(new java.awt.Font("Monospaced", 1, 14)); // NOI18N + tbWarning.setRows(5); + tbWarning.setText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.tbWarning.text")); // NOI18N + tbWarning.setAutoscrolls(false); + spMainScrollPane.setViewportView(tbWarning); + + org.openide.awt.Mnemonics.setLocalizedText(lbTimeoutText, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbTimeoutText.text")); // NOI18N + lbTimeoutText.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbTimeoutText.toolTipText")); // NOI18N + lbTimeoutText.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + lbTimeoutTextMouseClicked(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(lbTimeoutHours, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbTimeoutHours.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(cbTimeoutEnabled, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.cbTimeoutEnabled.text")); // NOI18N + cbTimeoutEnabled.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.cbTimeoutEnabled.toolTipText")); // NOI18N + cbTimeoutEnabled.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + cbTimeoutEnabledItemStateChanged(evt); + } + }); + + spMaximumRetryAttempts.setModel(new javax.swing.SpinnerNumberModel(2, 0, 9999999, 1)); + spMaximumRetryAttempts.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.spMaximumRetryAttempts.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(lbConcurrentJobsPerCase, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbConcurrentJobsPerCase.text")); // NOI18N + lbConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbConcurrentJobsPerCase.toolTipText")); // NOI18N + lbConcurrentJobsPerCase.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); + + spConcurrentJobsPerCase.setModel(new javax.swing.SpinnerNumberModel(3, 1, 100, 1)); + spConcurrentJobsPerCase.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbConcurrentJobsPerCase.toolTipText")); // NOI18N + + spTimeoutHours.setModel(new javax.swing.SpinnerNumberModel(60, 1, 100000, 1)); + spTimeoutHours.setToolTipText(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.spTimeoutHours.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(lbSecondsBetweenJobsSeconds, org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.lbSecondsBetweenJobsSeconds.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(213, 213, 213) + .addComponent(filler1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(58, 58, 58)) + .addGroup(layout.createSequentialGroup() + .addGap(16, 16, 16) + .addComponent(spMainScrollPane) + .addGap(16, 16, 16)) + .addGroup(layout.createSequentialGroup() + .addGap(16, 16, 16) + .addComponent(cbTimeoutEnabled) + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbSecondsBetweenJobs) + .addComponent(lbTimeoutText) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbRetriesAllowed) + .addComponent(lbConcurrentJobsPerCase) + .addComponent(lbNumberOfThreads)) + .addGap(25, 25, 25) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbSecondsBetweenJobsSeconds) + .addComponent(lbTimeoutHours))) + .addGroup(layout.createSequentialGroup() + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 91, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(lbRestartRequired))))) + .addContainerGap(18, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(20, 20, 20) + .addComponent(spMainScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbSecondsBetweenJobs) + .addComponent(spSecondsBetweenJobs, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbSecondsBetweenJobsSeconds)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbTimeoutText) + .addComponent(lbTimeoutHours) + .addComponent(spTimeoutHours, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(cbTimeoutEnabled)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spMaximumRetryAttempts, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbRetriesAllowed)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(spConcurrentJobsPerCase, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbConcurrentJobsPerCase)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(lbRestartRequired) + .addComponent(lbNumberOfThreads) + .addComponent(numberOfFileIngestThreadsComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(241, Short.MAX_VALUE)) + ); + + spMaximumRetryAttempts.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(AdvancedOptionsPanel.class, "AdvancedOptionsPanel.spMaximumRetryAttempts.AccessibleContext.accessibleDescription")); // NOI18N + }// //GEN-END:initComponents + + private void cbTimeoutEnabledItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cbTimeoutEnabledItemStateChanged + setCheckboxEnabledState(); + }//GEN-LAST:event_cbTimeoutEnabledItemStateChanged + + private void lbTimeoutTextMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_lbTimeoutTextMouseClicked + if (cbTimeoutEnabled.isEnabled()) { + if (cbTimeoutEnabled.isSelected()) { + spTimeoutHours.setEnabled(true); + lbTimeoutHours.setEnabled(true); + cbTimeoutEnabled.setSelected(false); + } else { + spTimeoutHours.setEnabled(false); + lbTimeoutHours.setEnabled(false); + cbTimeoutEnabled.setSelected(true); + } + } + }//GEN-LAST:event_lbTimeoutTextMouseClicked + + private void numberOfFileIngestThreadsComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numberOfFileIngestThreadsComboBoxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_numberOfFileIngestThreadsComboBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox cbTimeoutEnabled; + private javax.swing.Box.Filler filler1; + private javax.swing.JLabel lbConcurrentJobsPerCase; + private javax.swing.JLabel lbNumberOfThreads; + private javax.swing.JLabel lbRestartRequired; + private javax.swing.JLabel lbRetriesAllowed; + private javax.swing.JLabel lbSecondsBetweenJobs; + private javax.swing.JLabel lbSecondsBetweenJobsSeconds; + private javax.swing.JLabel lbTimeoutHours; + private javax.swing.JLabel lbTimeoutText; + private javax.swing.JComboBox numberOfFileIngestThreadsComboBox; + private javax.swing.JSpinner spConcurrentJobsPerCase; + private javax.swing.JScrollPane spMainScrollPane; + private javax.swing.JSpinner spMaximumRetryAttempts; + private javax.swing.JSpinner spSecondsBetweenJobs; + private javax.swing.JSpinner spTimeoutHours; + private javax.swing.JTextArea tbWarning; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/configuration/Bundle.properties b/Core/src/org/sleuthkit/autopsy/configuration/Bundle.properties new file mode 100644 index 0000000000..6ebc54d1f0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/Bundle.properties @@ -0,0 +1,146 @@ +OptionsCategory_Name_General=Viking +OptionsCategory_Keywords_General=Viking Options +VikingOptionsPanel.restartRequiredLabel.text=Application restart required to take effect. +VikingOptionsPanel.jRadioButtonCopyFiles.text=File Copy mode +GeneralOptionsPanelController.moduleErr=Module Error +GeneralOptionsPanelController.moduleErr.msg=A module caused an error listening to GeneralOptionsPanelController updates. See log to determine which module. Some data could be incomplete. +VikingOptionsPanel.ImageDirectoryUnspecified=Shared images folder must be set +VikingOptionsPanel.ResultsDirectoryUnspecified=Shared results folder must be set +VikingOptionsPanel.PathInvalid=Path is not valid +VikingOptionsPanel.globalSettingsCheckbox.text=Use shared configuration in folder: +VikingOptionsPanel.browseGlobalSettingsButton.text=Browse +VikingOptionsPanel.globalSettingsTextField.text= +VikingOptionsPanel.globalSettingsErrorTextField.text= +VikingOptionsPanel.EmptySettingsDirectory=Enter path to settings directory +AIMIngestSettingsPanel.jButtonEditIngestSettings.toolTipText=Ingest job settings for the automated processing mode context. +AIMIngestSettingsPanel.jButtonEditIngestSettings.text=Ingest Module Settings +AIMIngestSettingsPanel.globalSettingsErrorTextField.text= +AIMIngestSettingsPanel.browseGlobalSettingsButton.text=Browse +AIMIngestSettingsPanel.globalSettingsTextField.text= +AIMIngestSettingsPanel.globalSettingsCheckbox.text=Use shared configuration in folder: +AIMIngestSettingsPanel.lbSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. +AIMIngestSettingsPanel.lbSecondsBetweenJobs.text=Number of seconds to wait between jobs: +AIMIngestSettingsPanel.spSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. +AIMIngestSettingsPanel.jButton1.text=Download shared settings +AIMIngestSettingsPanel.jLabel1.text=Download the current shared setting (highly recommended to do this before editing) +VikingOptionsDialog.jButton1.text=jButton1 +VikingOptionsDialog.jCheckBox1.text=jCheckBox1 +VikingOptionsDialog.jLabel1.text=jLabel1 +VikingOptionsPanel.restartRequiredLabel.text=Application restart required +VikingOptionsPanel.validationErrMsg.incomplete=Fill in all values +VikingOptionsPanel.validationErrMsg.invalidDatabasePort=Invalid database port number +VikingOptionsPanel.validationErrMsg.invalidMessageServicePort=Invalid message service port number +VikingOptionsPanel.validationErrMsg.invalidIndexingServerPort=Invalid Solr server port number +VikingOptionsPanel.ErrorSettingDefaultFolder=Error creating default folder +OpenVikingOptionsPanelAction.name=Viking Options +VikingOptionsPanel.KeywordSearchNull=Cannot find Keyword Search service +VikingOptionsPanel.InvalidPortNumber=Invalid port number. +VikingOptionsPanel.AdvancedOptionsPanel.Title=Advanced Settings +VikingOptionsPanel.CannotAccess=Cannot access +VikingOptionsPanel.CheckPermissions=Check permissions. +VikingOptionsPanel.MustRestart=Viking must be restarted for new configuration to take effect +TranslationSettingsPanel.lbIpAddress.text=IP Address or Hostname of Machine Translation Service +TranslationSettingsPanel.tbIpAddress.text= +TranslationSettingsPanel.tbOops.text= +TranslationSettingsPanel.bnTest.text=Test +TranslationSettingsPanel.lbTestIpAddress.text= +TranslationSettingsPanel.IpAddress.toolTipText=IP Address or Hostname of Machine Translation Service +TranslationSettingsPanel.IpAddress.CannotConnect=Cannot connect to machine translation service. +AdvancedOptionsPanel.lbSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. +AdvancedOptionsPanel.lbSecondsBetweenJobs.text=Time to wait between jobs: +AdvancedOptionsPanel.spSecondsBetweenJobs.toolTipText=Increase this value if database locks cause problems. It gives a little more time for finalizing. +AdvancedOptionsPanel.lbRetriesAllowed.text=Maximum job retries allowed: +AdvancedOptionsPanel.tbWarning.text=WARNING: Ensure you know what you are doing before modifying\nthese values. Responsible use can improve system performance,\nlikewise, misuse can cause system degradation. +AdvancedOptionsPanel.lbNumberOfThreads.text=Number of threads to use for file ingest: +AdvancedOptionsPanel.lbRestartRequired.text=Application restart required to take effect. +AdvancedOptionsPanel.lbTimeoutHours.text=hour(s) +AdvancedOptionsPanel.spMaximumRetryAttempts.toolTipText=The maximum number of times Automated ingest will attempt to reprocess a job if processing fails. +AdvancedOptionsPanel.spMaximumRetryAttempts.AccessibleContext.accessibleDescription=Maximum number of retries allowed in Automated Ingest Mode +AdvancedOptionsPanel.lbRetriesAllowed.toolTipText=The maximum number of times Automated ingest will attempt to reprocess a job if processing fails. +AdvancedOptionsPanel.threadCountLabel.text=For this computer, a maximum of {0} file ingest threads should be used. +AdvancedOptionsPanel.ItemDisabled.text=Item disabled based upon current Viking mode. +AdvancedOptionsPanel.lbTimeoutText.toolTipText=Maximum allowable run time. Can kill "stuck" modules. +AdvancedOptionsPanel.lbTimeoutText.text=Enable timeout to allow modules to automatically terminate after a set amount of time: +AdvancedOptionsPanel.cbTimeoutEnabled.toolTipText=Maximum allowable run time. Can kill "stuck" modules. +AdvancedOptionsPanel.cbTimeoutEnabled.text= +AdvancedOptionsPanel.lbTimeoutText.text=Modules automatically terminate after: +AdvancedOptionsPanel.lbConcurrentJobsPerCase.toolTipText=Controls how many ingest nodes may operate concurrently on a case if there are other cases to process. +AdvancedOptionsPanel.lbConcurrentJobsPerCase.text=Maximum concurrent jobs per case: +AdvancedOptionsPanel.spTimeoutHours.toolTipText=The maximum number of times Automated ingest will attempt to reprocess a job if processing fails. +AdvancedOptionsPanel.lbSecondsBetweenJobsSeconds.text=seconds +VikingOptionsPanel.FileExportRules.text=File Export Rules +OptionsPanel.bnSave.text=Save +OptionsPanel.nodePanel.TabConstraints.tabTitle=Node Configuration +OptionsPanel.multiUserRestartLabel.text=Application restart required after any changes +OptionsPanel.tbOops.text=oops +OptionsPanel.cbEnableMultiUser.text=Enable Multi-user cases +OptionsPanel.lbTestDbWarning.text= +OptionsPanel.lbTestDatabase.text= +OptionsPanel.bnTestDatabase.text=Test +OptionsPanel.lbDatabaseSettings.text=Database Settings +OptionsPanel.tbDbPassword.toolTipText=Password +OptionsPanel.tbDbPassword.text= +OptionsPanel.tbDbUsername.toolTipText=User Name +OptionsPanel.tbDbUsername.text= +OptionsPanel.bnFileExport.text=File Export Settings +OptionsPanel.bnAdvancedSettings.text=Advanced Settings +OptionsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the automated processing mode context. +OptionsPanel.bnEditIngestSettings.text=Ingest Module Settings +OptionsPanel.jPanelIngestSettings.border.title=Automated Ingest Settings +OptionsPanel.configButtonErrorTextField.text=configButtonErrorTextField +OptionsPanel.jLabelTaskDescription.text=jLabel1 +OptionsPanel.jLabelCurrentTask.text=Current task: +OptionsPanel.downloadButton.text=Download Config +OptionsPanel.uploadButton.text=Save & Upload Config +OptionsPanel.masterNodeCheckBox.text=Use this node as a master node that can upload settings +OptionsPanel.tbDbPort.toolTipText=Port Number +OptionsPanel.tbDbPort.text= +OptionsPanel.sharedSettingsErrorTextField.text=globalSettingsErrorTextField +OptionsPanel.tbDbHostname.toolTipText=Hostname or IP Address +OptionsPanel.tbDbHostname.text= +OptionsPanel.browseSharedSettingsButton.text=Browse +OptionsPanel.lbTestSolrWarning.text= +OptionsPanel.lbTestSolr.text= +OptionsPanel.bnTestSolr.text=Test +OptionsPanel.tbSolrPort.toolTipText=Port Number +OptionsPanel.tbSolrHostname.toolTipText=Hostname or IP Address +OptionsPanel.lbSolrSettings.text=Solr Settings +OptionsPanel.sharedSettingsTextField.text= +OptionsPanel.sharedConfigCheckbox.text=Use shared configuration in folder: +OptionsPanel.jPanelSharedConfig.border.title=Shared Configuration +OptionsPanel.multiUserErrorTextField.text=jTextField1 +OptionsPanel.jLabelInvalidResultsFolder.text=jLabelInvalidResultsFolder +OptionsPanel.jLabelInvalidImageFolder.text=jLabelInvalidImageFolder +OptionsPanel.jLabelVikingDiagram.text= +OptionsPanel.browseOutputFolderButton.text=Browse +OptionsPanel.lbTestMessageWarning.text= +OptionsPanel.outputPathTextField.toolTipText=Results folder for automated processing, i.e., the location where results folders will be created by automated processing mode Viking for presentation to the user in review mode Viking. Also, cases that were single-user and were converted to multi-user cases will end up here. +OptionsPanel.outputPathTextField.text= +OptionsPanel.lbTestMessageService.text= +OptionsPanel.jLabelSelectOutputFolder.text=Select shared results folder: +OptionsPanel.bnTestMessageService.text=Test +OptionsPanel.tbMsgPassword.toolTipText=Password (optional) +OptionsPanel.tbMsgPassword.text= +OptionsPanel.tbMsgPort.toolTipText=Port Number +OptionsPanel.tbMsgPort.text= +OptionsPanel.tbMsgUsername.toolTipText=User Name (optional) +OptionsPanel.tbMsgUsername.text= +OptionsPanel.tbMsgHostname.toolTipText=Hostname or IP Address +OptionsPanel.tbMsgHostname.text= +OptionsPanel.lbMessageServiceSettings.text=ActiveMQ Message Service Settings +OptionsPanel.multiUserPanel.TabConstraints.tabTitle=Multi-user Settings +OptionsPanel.browseInputFolderButton.text=Browse +OptionsPanel.inputPathTextField.toolTipText=Input folder for automated processing, i.e., the location where input case folders will be created for ingest by automated processing mode Viking +OptionsPanel.inputPathTextField.text= +OptionsPanel.jLabelSelectInputFolder.text=Select shared images folder: +OptionsPanel.jRadioButtonReview.toolTipText=Review cases created in automated processing mode +OptionsPanel.jRadioButtonReview.text=Reviewer +OptionsPanel.jRadioButtonAutomated.toolTipText=Automatically detect new data sources and create cases. +OptionsPanel.jRadioButtonAutomated.text=Automated ingest +OptionsPanel.jRadioButtonCopyFilesMode.text=Single user case import +OptionsPanel.jRadioButtonStandalone.toolTipText=Run as Autopsy with advanced features. +OptionsPanel.jRadioButtonStandalone.text=Standalone +OptionsPanel.restartRequiredNodeLabel.text=Application restart required +OptionsPanel.jLabelSelectMode.text=Select mode: +OptionsPanel.bnCancel.text=Cancel +OptionsPanel.jPanelNodeType.border.title=Node Type Setup diff --git a/Core/src/org/sleuthkit/autopsy/configuration/OpenOptionsPanelAction.java b/Core/src/org/sleuthkit/autopsy/configuration/OpenOptionsPanelAction.java new file mode 100644 index 0000000000..7c515645e3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/OpenOptionsPanelAction.java @@ -0,0 +1,66 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.configuration; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.SystemAction; +import java.awt.Cursor; +import org.openide.windows.WindowManager; + +@ActionID(category = "Tools", id = "viking.configuration.OpenVikingOptionsPanelAction") +@ActionRegistration( + displayName = "#CTL_OpenVikingOptionsPanelAction", + lazy = false) +@ActionReferences({ + @ActionReference(path = "Menu/Tools", position = 1600) +}) +@NbBundle.Messages(value = "CTL_OpenVikingOptionsPanelAction=Viking Options") + +/** + * Custom Viking options action that opens up the Viking options panel. + * Contributes an entry in Tools menu + */ +public final class OpenOptionsPanelAction extends SystemAction implements ActionListener { + + private static final String ACTION_NAME = NbBundle.getMessage(OpenOptionsPanelAction.class, "OpenVikingOptionsPanelAction.name"); + + @Override + public void actionPerformed(ActionEvent e) { + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + OptionsDialog dialog = new OptionsDialog(null, true); + WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + @Override + public String getName() { + return ACTION_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configuration/OptionsDialog.java b/Core/src/org/sleuthkit/autopsy/configuration/OptionsDialog.java new file mode 100644 index 0000000000..161cb6350f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/OptionsDialog.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.configuration; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Cursor; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.KeyStroke; +import org.openide.util.ImageUtilities; + +/** + */ +public class OptionsDialog extends JDialog { + + private static final String TITLE = "Options"; + private static final Dimension DIMENSIONS = new Dimension(791, 763); + + /** + * Constructs an instance of the dialog with its own frame. Could be modal. + * + * @param owner - the owner of this dialog. If this dialog should be + * modal, the owner gets set to non modal. + * @param shouldBeModal - true if this should be modal, false otherwise. + */ + public OptionsDialog(Container owner, Boolean shouldBeModal) { + super((Window) owner, TITLE, Dialog.ModalityType.MODELESS); + setIconImage(ImageUtilities.loadImage("viking/images/viking16.gif", false)); //NON-NLS + if (shouldBeModal && owner instanceof JDialog) { // if called from a modal dialog, manipulate the parent be just under this in z order, and not modal. + final JDialog pseudoOwner = (JDialog) owner; + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { // Put it back to how it was before we manipulated it. + pseudoOwner.setVisible(false); + pseudoOwner.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); + pseudoOwner.toFront(); + pseudoOwner.setVisible(true); + } + }); + pseudoOwner.setVisible(false); + pseudoOwner.setModalityType(Dialog.ModalityType.MODELESS); + pseudoOwner.toFront(); + pseudoOwner.repaint(); + pseudoOwner.setVisible(true); + } + setResizable(true); + setLayout(new BorderLayout()); + this.getRootPane().registerKeyboardAction(e -> { + this.dispose(); + }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + add(new OptionsPanel(this)); + pack(); + setLocationRelativeTo(owner); + setResizable(false); + if (shouldBeModal) { // if called from a modal dialog, become modal, otherwise don't. + setModal(true); + } + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + setVisible(true); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.form b/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.form new file mode 100644 index 0000000000..21eeaaa6ad --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.form @@ -0,0 +1,1237 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.java b/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.java new file mode 100644 index 0000000000..e0ec8e44ab --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/OptionsPanel.java @@ -0,0 +1,2434 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.configuration; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JTextField; +import javax.swing.SwingWorker; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.corecomponents.TextPrompt; +import org.sleuthkit.autopsy.events.MessageServiceConnectionInfo; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettingsPanel; +import org.sleuthkit.datamodel.TskData; +import java.awt.Cursor; +import java.awt.Dimension; +import java.nio.file.Paths; +import javax.swing.ImageIcon; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; +import org.sleuthkit.autopsy.events.MessageServiceException; +import viking.autoingest.FileExporterSettingsPanel; +import javax.swing.JScrollPane; + +/** + * + */ +public class OptionsPanel extends javax.swing.JPanel { + + private final JDialog parent; + private final JFileChooser fc = new JFileChooser(); + private final Collection textBoxes = new ArrayList<>(); + private TextBoxChangedListener textBoxChangedListener; + private static final String HOST_NAME_OR_IP_PROMPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbDbHostname.toolTipText"); + private static final String PORT_PROMPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbDbPort.toolTipText"); + private static final String USER_NAME_PROMPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbDbUsername.toolTipText"); + private static final String PASSWORD_PROMPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbDbPassword.toolTipText"); + private static final String USER_NAME_PROMPT_OPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbMsgUsername.toolTipText"); + private static final String PASSWORD_PROMPT_OPT = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.tbMsgPassword.toolTipText"); + private static final String INCOMPLETE_SETTINGS_MSG = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.validationErrMsg.incomplete"); + private static final String INVALID_DB_PORT_MSG = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.validationErrMsg.invalidDatabasePort"); + private static final String INVALID_MESSAGE_SERVICE_PORT_MSG = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.validationErrMsg.invalidMessageServicePort"); + private static final String INVALID_INDEXING_SERVER_PORT_MSG = NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.validationErrMsg.invalidIndexingServerPort"); + private static final long serialVersionUID = 1L; + private final ImageIcon goodIcon; + private final ImageIcon badIcon; + private static final Logger logger = Logger.getLogger(OptionsPanel.class.getName()); + private final TranslationSettingsPanel translationSettingsPanel; + private Integer oldIngestThreads; + + enum OptionsUiMode { + + STANDALONE, UTILITY, AIM, REVIEW, DOWNLOADING_CONFIGURATION + }; + + /** + * Creates new form VikingOptionsPanel + */ + public OptionsPanel(JDialog parent) { + this.parent = parent; + initComponents(); + initMultiUserPanel(); + translationSettingsPanel = new TranslationSettingsPanel(); + mainTabPane.addTab("Machine Translation", translationSettingsPanel); + + load(true); + sharedSettingsTextField.getDocument().addDocumentListener(new MyDocumentListener()); + inputPathTextField.getDocument().addDocumentListener(new MyDocumentListener()); + outputPathTextField.getDocument().addDocumentListener(new MyDocumentListener()); + tbSolrHostname.getDocument().addDocumentListener(new MyDocumentListener()); + tbSolrPort.getDocument().addDocumentListener(new MyDocumentListener()); + + jLabelInvalidImageFolder.setText(""); + jLabelInvalidResultsFolder.setText(""); + sharedSettingsErrorTextField.setText(""); + jLabelTaskDescription.setText(""); + configButtonErrorTextField.setText(""); + multiUserErrorTextField.setText(""); + + pbTaskInProgress.setEnabled(false); + jLabelCurrentTask.setEnabled(false); + jLabelTaskDescription.setEnabled(false); + + goodIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/good.png", false)); + badIcon = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/bad.png", false)); + + this.oldIngestThreads = UserPreferences.numberOfFileIngestThreads(); + cbEnableMultiUserItemStateChanged(null); + } + + private class MyDocumentListener implements DocumentListener { + + @Override + public void changedUpdate(DocumentEvent e) { + validateSettings(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + validateSettings(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + validateSettings(); + } + }; + + /** + * Load Viking mode from persistent storage. + * + * @param inStartup True if we're doing the initial population of the UI + */ + final void load(boolean inStartup) { + loadMultiUserSettings(); + if (inStartup) { + VikingUserPreferences.SelectedVikingMode storedVikingMode = VikingUserPreferences.getVikingMode(); + inputPathTextField.requestFocusInWindow(); + if (storedVikingMode == VikingUserPreferences.SelectedVikingMode.REVIEW) { + jRadioButtonReview.setSelected(true); + enableOptionsBasedOnMode(OptionsUiMode.REVIEW); + } else if (storedVikingMode == VikingUserPreferences.SelectedVikingMode.AUTOMATED) { + jRadioButtonAutomated.setSelected(true); + enableOptionsBasedOnMode(OptionsUiMode.AIM); + } else if (storedVikingMode == VikingUserPreferences.SelectedVikingMode.COPYFILES) { + jRadioButtonCopyFilesMode.setSelected(true); + enableOptionsBasedOnMode(OptionsUiMode.UTILITY); + } else { + jRadioButtonStandalone.setSelected(true); + enableOptionsBasedOnMode(OptionsUiMode.STANDALONE); + } + } + + String images = VikingUserPreferences.getAutoModeImageFolder(); + if (images != null) { + inputPathTextField.setText(images); + } else { + inputPathTextField.setText(""); + } + + String results = VikingUserPreferences.getAutoModeResultsFolder(); + if (results != null) { + outputPathTextField.setText(results); + } else { + outputPathTextField.setText(""); + } + + if (inStartup) { + sharedConfigCheckbox.setSelected(VikingUserPreferences.getSharedConfigEnabled()); + String sharedSettingsFolder = VikingUserPreferences.getSharedConfigFolder(); + if (sharedSettingsFolder != null) { + sharedSettingsTextField.setText(sharedSettingsFolder); + } else { + String folder = getDefaultSharedFolder(); + sharedSettingsTextField.setText(folder); + } + + masterNodeCheckBox.setSelected(VikingUserPreferences.getSharedConfigMaster()); + setEnabledStateForSharedConfiguration(); + } + + validateSettings(); + } + + /** + * Get the default location for the shared configuration folder. Currently + * this is a subfolder of the shared images folder. + * + * @return The default subfolder name, or an empty string if the base folder + * is not set + */ + private String getDefaultSharedFolder() { + + String images = inputPathTextField.getText().trim(); + if (images == null || images.isEmpty()) { + return ""; + } + File sharedFolder = new File(images, "sharedConfiguration"); + if (!sharedFolder.exists()) { + try { + sharedFolder.mkdir(); + return sharedFolder.getAbsolutePath(); + } catch (Exception ex) { + sharedSettingsErrorTextField.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.ErrorSettingDefaultFolder")); + return ""; + } + } + return sharedFolder.getAbsolutePath(); + } + + /** + * Save Viking mode to persistent storage. + */ + void store() { + translationSettingsPanel.store(); + if (jRadioButtonStandalone.isSelected()) { + VikingUserPreferences.setVikingMode(VikingUserPreferences.SelectedVikingMode.STANDALONE); + } else if (jRadioButtonAutomated.isSelected()) { + boolean needsSaving = false; + String thePath = VikingUserPreferences.getAutoModeImageFolder(); + if (thePath != null && 0 != inputPathTextField.getText().compareTo(thePath)) { + needsSaving = true; + } + thePath = VikingUserPreferences.getAutoModeResultsFolder(); + if (thePath != null && 0 != outputPathTextField.getText().compareTo(thePath)) { + needsSaving = true; + } + if (needsSaving) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.MustRestart"), + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.restartRequiredLabel.text"), + JOptionPane.WARNING_MESSAGE); + } + + VikingUserPreferences.setVikingMode(VikingUserPreferences.SelectedVikingMode.AUTOMATED); + String imageFolderPath = getNormalizedFolderPath(inputPathTextField.getText().trim()); + VikingUserPreferences.setAutoModeImageFolder(imageFolderPath); + String resultsFolderPath = getNormalizedFolderPath(outputPathTextField.getText().trim()); + VikingUserPreferences.setAutoModeResultsFolder(resultsFolderPath); + VikingUserPreferences.setSharedConfigEnabled(sharedConfigCheckbox.isSelected()); + if (sharedConfigCheckbox.isSelected()) { + String globalSettingsPath = getNormalizedFolderPath(sharedSettingsTextField.getText().trim()); + VikingUserPreferences.setSharedConfigFolder(globalSettingsPath); + VikingUserPreferences.setSharedConfigMaster(masterNodeCheckBox.isSelected()); + } + } else if (jRadioButtonCopyFilesMode.isSelected()) { + + boolean needsSaving = false; + String thePath = VikingUserPreferences.getAutoModeImageFolder(); + if (thePath != null && 0 != inputPathTextField.getText().compareTo(thePath)) { + needsSaving = true; + } + thePath = VikingUserPreferences.getAutoModeResultsFolder(); + if (thePath != null && 0 != outputPathTextField.getText().compareTo(thePath)) { + needsSaving = true; + } + if (needsSaving) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.MustRestart"), + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.restartRequiredLabel.text"), + JOptionPane.WARNING_MESSAGE); + } + + VikingUserPreferences.setVikingMode(VikingUserPreferences.SelectedVikingMode.COPYFILES); + String imageFolderPath = getNormalizedFolderPath(inputPathTextField.getText().trim()); + VikingUserPreferences.setAutoModeImageFolder(imageFolderPath); + String resultsFolderPath = getNormalizedFolderPath(outputPathTextField.getText().trim()); + VikingUserPreferences.setAutoModeResultsFolder(resultsFolderPath); + VikingUserPreferences.setSharedConfigEnabled(sharedConfigCheckbox.isSelected()); + if (sharedConfigCheckbox.isSelected()) { + String globalSettingsPath = getNormalizedFolderPath(sharedSettingsTextField.getText().trim()); + VikingUserPreferences.setSharedConfigFolder(globalSettingsPath); + VikingUserPreferences.setSharedConfigMaster(masterNodeCheckBox.isSelected()); + } + } else if (jRadioButtonReview.isSelected()) { + String thePath = VikingUserPreferences.getAutoModeResultsFolder(); + if (thePath != null && 0 != outputPathTextField.getText().compareTo(thePath)) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.MustRestart"), + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.restartRequiredLabel.text"), + JOptionPane.WARNING_MESSAGE); + } + + VikingUserPreferences.setVikingMode(VikingUserPreferences.SelectedVikingMode.REVIEW); + String resultsFolderPath = getNormalizedFolderPath(outputPathTextField.getText().trim()); + VikingUserPreferences.setAutoModeResultsFolder(resultsFolderPath); + } + + storeMultiUserSettings(); + } + + void validateSettings() { + if (valid()) { + bnSave.setEnabled(true); + + if (validSharedConfigSettings()) { + downloadButton.setEnabled(true); + if (masterNodeCheckBox.isEnabled() && masterNodeCheckBox.isSelected()) { + uploadButton.setEnabled(true); + } else { + uploadButton.setEnabled(false); + } + } else { + downloadButton.setEnabled(false); + uploadButton.setEnabled(false); + } + displaySharedConfigButtonText(); + } else { + bnSave.setEnabled(false); + uploadButton.setEnabled(false); + if (validSharedConfigSettings()) { + downloadButton.setEnabled(true); + } else { + downloadButton.setEnabled(false); + } + displaySharedConfigButtonText(); + } + } + + /** + * Validate current panel settings. + */ + boolean valid() { + jLabelInvalidImageFolder.setVisible(false); + jLabelInvalidResultsFolder.setVisible(false); + boolean isValidNodePanel = true; + + boolean isValidMultiuserPanel = validateMultiUserPanel(); + + if (jRadioButtonAutomated.isSelected() || jRadioButtonCopyFilesMode.isSelected()) { + if (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected() && !validSharedConfigSettings()) { + isValidNodePanel = false; + } + } + + if (!isValidMultiuserPanel) { + mainTabPane.setForegroundAt(0, Color.red); + } else { + mainTabPane.setForegroundAt(0, Color.black); + } + + if (!isValidNodePanel) { + mainTabPane.setForegroundAt(1, Color.red); + } else { + mainTabPane.setForegroundAt(1, Color.black); + } + + return isValidNodePanel && isValidMultiuserPanel; + } + + /** + * Check everything that is needed to enable the upload or download config + * buttons is set (except the master node checkbox for upload). + * + * @return + */ + boolean validSharedConfigSettings() { + // Check for: + // - shared config checkbox enabled and checked + // - valid shared config folder entered + // - mulit-user settings enabled + // - solr address + // - solr port + return (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected() + && cbEnableMultiUser.isSelected() && indexingServerSettingsAreValid() + && !tbSolrHostname.getText().trim().isEmpty()); + } + + /** + * + */ + void displaySharedConfigButtonText() { + if (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected()) { + if (!cbEnableMultiUser.isSelected()) { + configButtonErrorTextField.setText("Multi-user cases must be enabled"); + } else if (!indexingServerSettingsAreValid() || tbSolrHostname.getText().trim().isEmpty()) { + configButtonErrorTextField.setText("Multi-user Settings->Solr Settings are missing/invalid"); + } else { + configButtonErrorTextField.setText(""); + } + } else { + configButtonErrorTextField.setText(""); + } + } + + /** + * Determines whether settings requiring a restart have changed, and also + * updates the UI. + * + * @return true if something has changed that will require a reset, false + * otherwise + */ + boolean isResetNeeded() { + + boolean resetNeeded = false; + + // Save the current values from the UI + String oldInputPath = inputPathTextField.getText().trim(); + String oldOutputPath = outputPathTextField.getText().trim(); + + boolean oldMultiUserSelected = cbEnableMultiUser.isSelected(); + String oldHostnameOrIp = tbDbHostname.getText().trim(); + String oldPortNumber = tbDbPort.getText().trim(); + String oldUsername = tbDbUsername.getText().trim(); + char[] oldPassword = tbDbPassword.getPassword(); + String oldMsgHost = tbMsgHostname.getText().trim(); + String oldMsgPort = tbMsgPort.getText().trim(); + String oldMsgUserName = tbMsgUsername.getText().trim(); + char[] oldMsgPassword = tbMsgPassword.getPassword(); + String oldIndexingServerHost = tbSolrHostname.getText().trim(); + String oldIndexingServerPort = tbSolrPort.getText().trim(); + + // Refresh UI + load(false); + + if (!oldInputPath.equals(inputPathTextField.getText().trim())) { + resetNeeded = true; + } + if (!oldOutputPath.equals(outputPathTextField.getText().trim())) { + resetNeeded = true; + } + if (!this.oldIngestThreads.equals(UserPreferences.numberOfFileIngestThreads())) { + resetNeeded = true; + } + + if (oldMultiUserSelected != cbEnableMultiUser.isSelected() + || !oldHostnameOrIp.equals(tbDbHostname.getText().trim()) + || !oldPortNumber.equals(tbDbPort.getText().trim()) + || !oldUsername.equals(tbDbUsername.getText().trim()) + || !oldMsgHost.equals(tbMsgHostname.getText().trim()) + || !oldMsgPort.equals(tbMsgPort.getText().trim()) + || !oldMsgUserName.equals(tbMsgUsername.getText().trim()) + || !oldIndexingServerHost.equals(tbSolrHostname.getText().trim()) + || !oldIndexingServerPort.equals(tbSolrPort.getText().trim()) + || !Arrays.equals(oldPassword, tbDbPassword.getPassword()) + || !Arrays.equals(oldMsgPassword, tbMsgPassword.getPassword())) { + resetNeeded = true; + } + + return resetNeeded; + } + + /** + * Normalizes a path to make sure there are no "space" characters at the end + * + * @param path Path to a directory + * + * @return Path without "space" characters at the end + */ + String normalizePath(String path) { + + while (path.length() > 0) { + if (path.charAt(path.length() - 1) == ' ') { + path = path.substring(0, path.length() - 1); + } else { + break; + } + } + return path; + } + + /** + * Validates that a path is valid and points to a folder. + * + * @param path A path to be validated + * + * @return boolean returns true if valid and points to a folder, false + * otherwise + */ + boolean isFolderPathValid(String path) { + try { + File file = new File(normalizePath(path)); + + // check if it's a symbolic link + if (Files.isSymbolicLink(file.toPath())) { + return true; + } + + // local folder + if (file.exists() && file.isDirectory()) { + return true; + } + } catch (Exception ex) { + // Files.isSymbolicLink (and other "files" methods) throw exceptions on seemingly innocent inputs. + // For example, it will throw an exception when either " " is last character in path or + // a path starting with ":". + // We can just ignore these exceptions as they occur in process of user typing in the path. + return false; + } + return false; + } + + /** + * Returns a path that was normalized by file system. + * + * @param path A path to be normalized. Normalization occurs inside a call + * to new File(). + * + * @return String returns normalized OS path + */ + String getNormalizedFolderPath(String path) { + // removes "/", "\", and " " characters at the end of path string. + // normalizePath() removes spaces at the end of path and a call to "new File()" + // internally formats the path string to remove "/" and "\" characters at the end of path. + File file = new File(normalizePath(path)); + return file.getPath(); + } + + /** + * Validate image path. Display warnings if invalid. + */ + boolean validateImagePath() { + + String inputPath = inputPathTextField.getText().trim(); + + if (inputPath.isEmpty()) { + jLabelInvalidImageFolder.setVisible(true); + jLabelInvalidImageFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.ImageDirectoryUnspecified")); + return false; + } + + if (!isFolderPathValid(inputPath)) { + jLabelInvalidImageFolder.setVisible(true); + jLabelInvalidImageFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.PathInvalid")); + return false; + } + + if (false == permissionsAppropriate(inputPath)) { + jLabelInvalidImageFolder.setVisible(true); + jLabelInvalidImageFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CannotAccess") + + " " + inputPath + " " + + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CheckPermissions")); + return false; + } + + jLabelInvalidImageFolder.setText(""); + return true; + } + + /** + * Validate results path. Display warnings if invalid. + */ + boolean validateResultsPath() { + + String outputPath = outputPathTextField.getText().trim(); + + if (outputPath.isEmpty()) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.ResultsDirectoryUnspecified")); + return false; + } + + if (!isFolderPathValid(outputPath)) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.PathInvalid")); + return false; + } + + if (false == permissionsAppropriate(outputPath)) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CannotAccess") + + " " + outputPath + " " + + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CheckPermissions")); + return false; + } + + jLabelInvalidResultsFolder.setText(""); + return true; + } + + /** + * Validate shared settings path. Display warnings if invalid. + */ + boolean validateSharedSettingsPath() { + String sharedSettingsPath = sharedSettingsTextField.getText().trim(); + + // Automatically valid if shared settings aren't selected + if (!sharedConfigCheckbox.isSelected()) { + return true; + } + + if (sharedSettingsPath.isEmpty()) { + sharedSettingsErrorTextField.setVisible(true); + sharedSettingsErrorTextField.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.EmptySettingsDirectory")); + return false; + } + + if (!isFolderPathValid(sharedSettingsPath)) { + sharedSettingsErrorTextField.setVisible(true); + sharedSettingsErrorTextField.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.PathInvalid")); + return false; + } + + if (false == permissionsAppropriate(sharedSettingsPath)) { + sharedSettingsErrorTextField.setVisible(true); + sharedSettingsErrorTextField.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CannotAccess") + + " " + sharedSettingsPath + " " + + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.CheckPermissions")); + return false; + } + + sharedSettingsErrorTextField.setText(""); + return true; + } + + private void displayIngestJobSettingsPanel() { + + IngestJobSettings ingestJobSettings = new IngestJobSettings(VikingUserPreferences.getAutoModeIngestModuleContextString()); + showWarnings(ingestJobSettings); + IngestJobSettingsPanel ingestJobSettingsPanel = new IngestJobSettingsPanel(ingestJobSettings); + + add(ingestJobSettingsPanel, BorderLayout.PAGE_START); + + if (JOptionPane.showConfirmDialog(null, ingestJobSettingsPanel, "Ingest Module Configuration", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION) { + // store the updated settings + ingestJobSettings = ingestJobSettingsPanel.getSettings(); + ingestJobSettings.save(); + showWarnings(ingestJobSettings); + } + } + + private static void showWarnings(IngestJobSettings ingestJobSettings) { + List warnings = ingestJobSettings.getWarnings(); + if (warnings.isEmpty() == false) { + StringBuilder warningMessage = new StringBuilder(); + for (String warning : warnings) { + warningMessage.append(warning).append("\n"); + } + JOptionPane.showMessageDialog(null, warningMessage.toString()); + } + } + + private void enableOptionsBasedOnMode(OptionsUiMode mode) { + if (mode != OptionsUiMode.DOWNLOADING_CONFIGURATION) { + jLabelSelectInputFolder.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM); + inputPathTextField.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM); + browseInputFolderButton.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM); + + jLabelSelectOutputFolder.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM || mode == OptionsUiMode.REVIEW); + outputPathTextField.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM || mode == OptionsUiMode.REVIEW); + browseOutputFolderButton.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM || mode == OptionsUiMode.REVIEW); + + jPanelSharedConfig.setEnabled(mode == OptionsUiMode.UTILITY || mode == OptionsUiMode.AIM); + + jPanelIngestSettings.setEnabled(mode == OptionsUiMode.AIM); + bnEditIngestSettings.setEnabled(mode == OptionsUiMode.AIM); + bnAdvancedSettings.setEnabled(mode == OptionsUiMode.AIM); + jPanelSharedConfig.setEnabled(mode == OptionsUiMode.AIM); + sharedConfigCheckbox.setEnabled(mode == OptionsUiMode.AIM); + masterNodeCheckBox.setEnabled(mode == OptionsUiMode.AIM && sharedConfigCheckbox.isSelected()); + bnFileExport.setEnabled(mode == OptionsUiMode.AIM); + sharedSettingsTextField.setEnabled(mode == OptionsUiMode.AIM); + downloadButton.setEnabled(mode == OptionsUiMode.AIM); + } else { + setEnabledState(false); + } + + } + + private OptionsUiMode getModeFromRadioButtons() { + if (jRadioButtonStandalone.isSelected()) { + return OptionsUiMode.STANDALONE; + } else if (jRadioButtonCopyFilesMode.isSelected()) { + return OptionsUiMode.UTILITY; + } else if (jRadioButtonAutomated.isSelected()) { + return OptionsUiMode.AIM; + } else { + return OptionsUiMode.REVIEW; + } + } + + // Multi-user panel code + // Copied from org.sleuthkit.autopsy.corecomponents.MultiUserSettingsPanel to + // prevent having to expose methods. + /** + * Used to listen for changes in text boxes. It lets the panel know things + * have been updated and that validation needs to happen. + */ + class TextBoxChangedListener implements DocumentListener { + + @Override + public void changedUpdate(DocumentEvent e) { + Object statusIcon = e.getDocument().getProperty("statusIcon"); + if (statusIcon != null) { + ((javax.swing.JLabel) statusIcon).setIcon(null); + } + validateSettings(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + Object statusIcon = e.getDocument().getProperty("statusIcon"); + if (statusIcon != null) { + ((javax.swing.JLabel) statusIcon).setIcon(null); + } + validateSettings(); + } + + @Override + + public void removeUpdate(DocumentEvent e) { + Object statusIcon = e.getDocument().getProperty("statusIcon"); + if (statusIcon != null) { + ((javax.swing.JLabel) statusIcon).setIcon(null); + } + validateSettings(); + } + } + + private void initMultiUserPanel() { + /** + * Add text prompts to all of the text fields. + */ + Collection textPrompts = new ArrayList<>(); + textPrompts.add(new TextPrompt(HOST_NAME_OR_IP_PROMPT, tbDbHostname)); + textPrompts.add(new TextPrompt(PORT_PROMPT, tbDbPort)); + textPrompts.add(new TextPrompt(USER_NAME_PROMPT, tbDbUsername)); + textPrompts.add(new TextPrompt(PASSWORD_PROMPT, tbDbPassword)); + textPrompts.add(new TextPrompt(HOST_NAME_OR_IP_PROMPT, tbMsgHostname)); + textPrompts.add(new TextPrompt(PORT_PROMPT, tbMsgPort)); + textPrompts.add(new TextPrompt(USER_NAME_PROMPT_OPT, tbMsgUsername)); + textPrompts.add(new TextPrompt(PASSWORD_PROMPT_OPT, tbMsgPassword)); + textPrompts.add(new TextPrompt(HOST_NAME_OR_IP_PROMPT, tbSolrHostname)); + textPrompts.add(new TextPrompt(PORT_PROMPT, tbSolrPort)); + configureTextPrompts(textPrompts); + + /* + * Set each textbox with a "statusIcon" property enabling the + * DocumentListeners to know which icon to erase when changes are made + */ + tbDbHostname.getDocument().putProperty("statusIcon", lbTestDatabase); + tbDbPort.getDocument().putProperty("statusIcon", lbTestDatabase); + tbDbUsername.getDocument().putProperty("statusIcon", lbTestDatabase); + tbDbPassword.getDocument().putProperty("statusIcon", lbTestDatabase); + + tbSolrHostname.getDocument().putProperty("statusIcon", lbTestSolr); + tbSolrPort.getDocument().putProperty("statusIcon", lbTestSolr); + + tbMsgHostname.getDocument().putProperty("statusIcon", lbTestMessageService); + tbMsgPort.getDocument().putProperty("statusIcon", lbTestMessageService); + tbMsgUsername.getDocument().putProperty("statusIcon", lbTestMessageService); + tbMsgPassword.getDocument().putProperty("statusIcon", lbTestMessageService); + + /// Register for notifications when the text boxes get updated. + textBoxChangedListener = new TextBoxChangedListener(); + textBoxes.add(tbDbHostname); + textBoxes.add(tbDbPort); + textBoxes.add(tbDbUsername); + textBoxes.add(tbDbPassword); + textBoxes.add(tbMsgHostname); + textBoxes.add(tbMsgPort); + textBoxes.add(tbMsgUsername); + textBoxes.add(tbMsgPassword); + textBoxes.add(tbSolrHostname); + textBoxes.add(tbSolrPort); + addMultiUserDocumentListeners(textBoxes, textBoxChangedListener); + + lbTestDatabase.setIcon(null); + lbTestSolr.setIcon(null); + lbTestMessageService.setIcon(null); + lbTestDbWarning.setText(""); + lbTestSolrWarning.setText(""); + lbTestMessageWarning.setText(""); + enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); + } + + void loadMultiUserSettings() { + lbTestDatabase.setIcon(null); + lbTestSolr.setIcon(null); + lbTestMessageService.setIcon(null); + lbTestDbWarning.setText(""); + lbTestSolrWarning.setText(""); + lbTestMessageWarning.setText(""); + try { + CaseDbConnectionInfo dbInfo = UserPreferences.getDatabaseConnectionInfo(); + tbDbHostname.setText(dbInfo.getHost().trim()); + tbDbPort.setText(dbInfo.getPort().trim()); + tbDbUsername.setText(dbInfo.getUserName().trim()); + tbDbPassword.setText(dbInfo.getPassword()); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS + } + + try { + MessageServiceConnectionInfo msgServiceInfo = UserPreferences.getMessageServiceConnectionInfo(); + tbMsgHostname.setText(msgServiceInfo.getHost().trim()); + tbMsgPort.setText(Integer.toString(msgServiceInfo.getPort())); + tbMsgUsername.setText(msgServiceInfo.getUserName().trim()); + tbMsgPassword.setText(msgServiceInfo.getPassword()); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS + } + + String indexingServerHost = UserPreferences.getIndexingServerHost().trim(); + if (!indexingServerHost.isEmpty()) { + tbSolrHostname.setText(indexingServerHost); + } + String indexingServerPort = UserPreferences.getIndexingServerPort().trim(); + if (portNumberIsValid(indexingServerPort)) { + tbSolrPort.setText(indexingServerPort); + } + + lbTestDatabase.setIcon(null); + lbTestSolr.setIcon(null); + lbTestMessageService.setIcon(null); + + bnTestDatabase.setEnabled(false); + bnTestSolr.setEnabled(false); + bnTestMessageService.setEnabled(false); + + cbEnableMultiUser.setSelected(UserPreferences.getIsMultiUserModeEnabled()); + this.valid(); // trigger validation to enable buttons based on current settings + } + + void storeMultiUserSettings() { + + boolean multiUserCasesEnabled = cbEnableMultiUser.isSelected(); + UserPreferences.setIsMultiUserModeEnabled(multiUserCasesEnabled); + if (multiUserCasesEnabled == false) { + return; + } + + /* + * Currently only supporting multi-user cases with PostgreSQL case + * databases. + */ + TskData.DbType dbType = TskData.DbType.POSTGRESQL; + CaseDbConnectionInfo info = new CaseDbConnectionInfo( + tbDbHostname.getText().trim(), + tbDbPort.getText().trim(), + tbDbUsername.getText().trim(), + new String(tbDbPassword.getPassword()), + dbType); + + try { + UserPreferences.setDatabaseConnectionInfo(info); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error saving case database connection info", ex); //NON-NLS + } + + int msgServicePort = 0; + try { + msgServicePort = Integer.parseInt(this.tbMsgPort.getText().trim()); + } catch (NumberFormatException ex) { + logger.log(Level.SEVERE, "Could not parse messaging service port setting", ex); + } + + MessageServiceConnectionInfo msgServiceInfo = new MessageServiceConnectionInfo( + tbMsgHostname.getText().trim(), + msgServicePort, + tbMsgUsername.getText().trim(), + new String(tbMsgPassword.getPassword())); + + try { + UserPreferences.setMessageServiceConnectionInfo(msgServiceInfo); + } catch (UserPreferencesException ex) { + logger.log(Level.SEVERE, "Error saving messaging service connection info", ex); //NON-NLS + } + UserPreferences.setIndexingServerHost(tbSolrHostname.getText().trim()); + UserPreferences.setIndexingServerPort(Integer.parseInt(tbSolrPort.getText().trim())); + } + + private boolean validateMultiUserPanel() { + tbOops.setText(""); + multiUserErrorTextField.setText(""); + if ((jRadioButtonAutomated.isSelected() || jRadioButtonReview.isSelected() || jRadioButtonCopyFilesMode.isSelected()) + && !cbEnableMultiUser.isSelected()) { + // AIM and Review mode both require multi-user settings to be enabled + tbOops.setText("Multi-user settings must be enabled in non-standalone modes"); + multiUserErrorTextField.setText("Multi-user settings must be enabled"); + return false; + } + + if (cbEnableMultiUser.isSelected()) { + return checkFieldsAndEnableButtons() + && databaseSettingsAreValid() + && indexingServerSettingsAreValid() + && messageServiceSettingsAreValid(); + } else { + return true; + } + } + + /** + * Tests whether or not values have been entered in all of the database + * settings text fields. + * + * @return True or false. + */ + private boolean databaseFieldsArePopulated() { + return !tbDbHostname.getText().trim().isEmpty() + && !tbDbPort.getText().trim().isEmpty() + && !tbDbUsername.getText().trim().isEmpty() + && tbDbPassword.getPassword().length != 0; + } + + /** + * Tests whether or not values have been entered in all of the Solr settings + * text fields. + * + * @return True or false. + */ + private boolean solrFieldsArePopulated() { + return !tbSolrHostname.getText().trim().isEmpty() + && !tbSolrPort.getText().trim().isEmpty(); + } + + /** + * Tests whether or not values have been entered in all of the required + * message service settings text fields. + * + * @return True or false. + */ + private boolean messageServiceFieldsArePopulated() { + + if ((tbMsgHostname.getText().trim().isEmpty()) + || (tbMsgPort.getText().trim().isEmpty())) { + return false; + } + + // user name and pw are optional, but make sure they are both set or both empty + boolean isUserSet = (tbMsgUsername.getText().trim().isEmpty() == false); + boolean isPwSet = (tbMsgPassword.getPassword().length != 0); + return (isUserSet == isPwSet); + } + + /** + * Tests whether or not all of the settings components are populated and + * sets the test buttons appropriately. + * + * @return True or false. + */ + boolean checkFieldsAndEnableButtons() { + boolean result = true; + + boolean dbPopulated = databaseFieldsArePopulated(); + boolean solrPopulated = solrFieldsArePopulated(); + boolean messageServicePopulated = messageServiceFieldsArePopulated(); + + // PostgreSQL Database + bnTestDatabase.setEnabled(dbPopulated && tbDbHostname.isEnabled()); + + // Solr Indexing + bnTestSolr.setEnabled(solrPopulated && tbSolrHostname.isEnabled()); + + // ActiveMQ Messaging + bnTestMessageService.setEnabled(messageServicePopulated && tbMsgHostname.isEnabled()); + + if (!dbPopulated || !solrPopulated || !messageServicePopulated) { + // We don't even have everything filled out + if (sharedConfigCheckbox.isSelected() && !masterNodeCheckBox.isSelected() && solrPopulated) { + result = true; + } else { + result = false; + tbOops.setText(INCOMPLETE_SETTINGS_MSG); + } + } + return result; + } + + /** + * Tests whether or not the database settings are valid. + * + * @return True or false. + */ + boolean databaseSettingsAreValid() { + if (portNumberIsValid(tbDbPort.getText().trim())) { + return true; + } else { + tbOops.setText(INVALID_DB_PORT_MSG); + return false; + } + } + + /** + * Tests whether or not the message service settings are valid. + * + * @return True or false. + */ + boolean messageServiceSettingsAreValid() { + if (!portNumberIsValid(tbMsgPort.getText().trim())) { + tbOops.setText(INVALID_MESSAGE_SERVICE_PORT_MSG); + return false; + } + return true; + } + + /** + * Tests whether or not the indexing server settings are valid. + * + * @return True or false. + */ + boolean indexingServerSettingsAreValid() { + if (!portNumberIsValid(tbSolrPort.getText().trim())) { + tbOops.setText(INVALID_INDEXING_SERVER_PORT_MSG); + return false; + } + + return true; + } + + /** + * Determines whether or not a port number is within the range of valid port + * numbers. + * + * @param portNumber The port number as a string. + * + * @return True or false. + */ + private static boolean portNumberIsValid(String portNumber) { + try { + int value = Integer.parseInt(portNumber); + if (value < 0 || value > 65535) { // invalid port numbers + return false; + } + } catch (NumberFormatException detailsNotImportant) { + return false; + } + return true; + } + + /** + * Sets the foreground color and transparency of a collection of text + * prompts. + * + * @param textPrompts The text prompts to configure. + */ + private static void configureTextPrompts(Collection textPrompts) { + float alpha = 0.9f; // Mostly opaque + for (TextPrompt textPrompt : textPrompts) { + textPrompt.setForeground(Color.LIGHT_GRAY); + textPrompt.changeAlpha(alpha); + } + } + + /** + * Adds a change listener to a collection of text fields. + * + * @param textFields The text fields. + * @param listener The change listener. + */ + private static void addMultiUserDocumentListeners(Collection textFields, TextBoxChangedListener listener) { + for (JTextField textField : textFields) { + textField.getDocument().addDocumentListener(listener); + } + } + + /** + * Enables/disables the multi-user settings, based upon input provided + * + * @param enabled true means enable, false means disable + */ + private static void enableMultiUserComponents(Collection textFields, boolean enabled) { + for (JTextField textField : textFields) { + textField.setEnabled(enabled); + } + } + + /** + * 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 + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + modeRadioButtons = new javax.swing.ButtonGroup(); + bnSave = new javax.swing.JButton(); + bnCancel = new javax.swing.JButton(); + mainTabPane = new javax.swing.JTabbedPane(); + multiUserPanel = new javax.swing.JPanel(); + pnMessagingSettings = new javax.swing.JPanel(); + lbMessageServiceSettings = new javax.swing.JLabel(); + tbMsgHostname = new javax.swing.JTextField(); + tbMsgUsername = new javax.swing.JTextField(); + tbMsgPort = new javax.swing.JTextField(); + tbMsgPassword = new javax.swing.JPasswordField(); + bnTestMessageService = new javax.swing.JButton(); + lbTestMessageService = new javax.swing.JLabel(); + lbTestMessageWarning = new javax.swing.JLabel(); + pnSolrSettings = new javax.swing.JPanel(); + lbSolrSettings = new javax.swing.JLabel(); + tbSolrHostname = new javax.swing.JTextField(); + tbSolrPort = new javax.swing.JTextField(); + bnTestSolr = new javax.swing.JButton(); + lbTestSolr = new javax.swing.JLabel(); + lbTestSolrWarning = new javax.swing.JLabel(); + pnDatabaseSettings = new javax.swing.JPanel(); + tbDbHostname = new javax.swing.JTextField(); + tbDbPort = new javax.swing.JTextField(); + tbDbUsername = new javax.swing.JTextField(); + tbDbPassword = new javax.swing.JPasswordField(); + lbDatabaseSettings = new javax.swing.JLabel(); + bnTestDatabase = new javax.swing.JButton(); + lbTestDatabase = new javax.swing.JLabel(); + lbTestDbWarning = new javax.swing.JLabel(); + cbEnableMultiUser = new javax.swing.JCheckBox(); + tbOops = new javax.swing.JTextField(); + multiUserRestartLabel = new javax.swing.JLabel(); + nodePanel = new javax.swing.JPanel(); + jPanelNodeType = new javax.swing.JPanel(); + jLabelSelectMode = new javax.swing.JLabel(); + restartRequiredNodeLabel = new javax.swing.JLabel(); + jRadioButtonStandalone = new javax.swing.JRadioButton(); + jRadioButtonCopyFilesMode = new javax.swing.JRadioButton(); + jRadioButtonAutomated = new javax.swing.JRadioButton(); + jRadioButtonReview = new javax.swing.JRadioButton(); + jLabelSelectInputFolder = new javax.swing.JLabel(); + inputPathTextField = new javax.swing.JTextField(); + browseInputFolderButton = new javax.swing.JButton(); + jLabelSelectOutputFolder = new javax.swing.JLabel(); + outputPathTextField = new javax.swing.JTextField(); + browseOutputFolderButton = new javax.swing.JButton(); + jLabelVikingDiagram = new javax.swing.JLabel(); + jLabelInvalidImageFolder = new javax.swing.JLabel(); + jLabelInvalidResultsFolder = new javax.swing.JLabel(); + multiUserErrorTextField = new javax.swing.JTextField(); + jPanelSharedConfig = new javax.swing.JPanel(); + sharedConfigCheckbox = new javax.swing.JCheckBox(); + sharedSettingsTextField = new javax.swing.JTextField(); + browseSharedSettingsButton = new javax.swing.JButton(); + sharedSettingsErrorTextField = new javax.swing.JTextField(); + masterNodeCheckBox = new javax.swing.JCheckBox(); + uploadButton = new javax.swing.JButton(); + downloadButton = new javax.swing.JButton(); + jLabelCurrentTask = new javax.swing.JLabel(); + pbTaskInProgress = new javax.swing.JProgressBar(); + jLabelTaskDescription = new javax.swing.JLabel(); + configButtonErrorTextField = new javax.swing.JTextField(); + jSeparator1 = new javax.swing.JSeparator(); + jPanelIngestSettings = new javax.swing.JPanel(); + bnEditIngestSettings = new javax.swing.JButton(); + bnAdvancedSettings = new javax.swing.JButton(); + bnFileExport = new javax.swing.JButton(); + + org.openide.awt.Mnemonics.setLocalizedText(bnSave, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnSave.text")); // NOI18N + bnSave.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnSaveActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnCancel.text")); // NOI18N + bnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnCancelActionPerformed(evt); + } + }); + + multiUserPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + pnMessagingSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + lbMessageServiceSettings.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(lbMessageServiceSettings, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbMessageServiceSettings.text")); // NOI18N + + tbMsgHostname.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbMsgHostname.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgHostname.text")); // NOI18N + tbMsgHostname.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgHostname.toolTipText")); // NOI18N + + tbMsgUsername.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbMsgUsername.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgUsername.text")); // NOI18N + tbMsgUsername.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgUsername.toolTipText")); // NOI18N + + tbMsgPort.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbMsgPort.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgPort.text")); // NOI18N + tbMsgPort.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgPort.toolTipText")); // NOI18N + + tbMsgPassword.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbMsgPassword.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgPassword.text")); // NOI18N + tbMsgPassword.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbMsgPassword.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnTestMessageService, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnTestMessageService.text")); // NOI18N + bnTestMessageService.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnTestMessageServiceActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(lbTestMessageService, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestMessageService.text")); // NOI18N + + lbTestMessageWarning.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(lbTestMessageWarning, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestMessageWarning.text")); // NOI18N + + javax.swing.GroupLayout pnMessagingSettingsLayout = new javax.swing.GroupLayout(pnMessagingSettings); + pnMessagingSettings.setLayout(pnMessagingSettingsLayout); + pnMessagingSettingsLayout.setHorizontalGroup( + pnMessagingSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnMessagingSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnMessagingSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnMessagingSettingsLayout.createSequentialGroup() + .addComponent(lbMessageServiceSettings) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnTestMessageService) + .addGap(18, 18, 18) + .addComponent(lbTestMessageService, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(tbMsgHostname, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(tbMsgUsername, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(tbMsgPort, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(tbMsgPassword) + .addGroup(pnMessagingSettingsLayout.createSequentialGroup() + .addComponent(lbTestMessageWarning) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + pnMessagingSettingsLayout.setVerticalGroup( + pnMessagingSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnMessagingSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnMessagingSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbMessageServiceSettings) + .addComponent(bnTestMessageService) + .addComponent(lbTestMessageService, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbMsgHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbMsgPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbMsgUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbMsgPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lbTestMessageWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pnSolrSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + lbSolrSettings.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(lbSolrSettings, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbSolrSettings.text")); // NOI18N + + tbSolrHostname.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbSolrHostname.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbSolrHostname.toolTipText")); // NOI18N + + tbSolrPort.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbSolrPort.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbSolrPort.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnTestSolr, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnTestSolr.text")); // NOI18N + bnTestSolr.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnTestSolrActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(lbTestSolr, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestSolr.text")); // NOI18N + + lbTestSolrWarning.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(lbTestSolrWarning, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestSolrWarning.text")); // NOI18N + + javax.swing.GroupLayout pnSolrSettingsLayout = new javax.swing.GroupLayout(pnSolrSettings); + pnSolrSettings.setLayout(pnSolrSettingsLayout); + pnSolrSettingsLayout.setHorizontalGroup( + pnSolrSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnSolrSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnSolrSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnSolrSettingsLayout.createSequentialGroup() + .addComponent(lbSolrSettings) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnTestSolr) + .addGap(18, 18, 18) + .addComponent(lbTestSolr, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(tbSolrHostname, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(tbSolrPort, javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(pnSolrSettingsLayout.createSequentialGroup() + .addComponent(lbTestSolrWarning) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + pnSolrSettingsLayout.setVerticalGroup( + pnSolrSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnSolrSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnSolrSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbSolrSettings) + .addComponent(bnTestSolr) + .addComponent(lbTestSolr, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbSolrHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE) + .addComponent(tbSolrPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lbTestSolrWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pnDatabaseSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + tbDbHostname.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbDbHostname.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbHostname.text")); // NOI18N + tbDbHostname.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbHostname.toolTipText")); // NOI18N + + tbDbPort.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbDbPort.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbPort.text")); // NOI18N + tbDbPort.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbPort.toolTipText")); // NOI18N + + tbDbUsername.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbDbUsername.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbUsername.text")); // NOI18N + tbDbUsername.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbUsername.toolTipText")); // NOI18N + + tbDbPassword.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + tbDbPassword.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbPassword.text")); // NOI18N + tbDbPassword.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbDbPassword.toolTipText")); // NOI18N + + lbDatabaseSettings.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(lbDatabaseSettings, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbDatabaseSettings.text")); // NOI18N + lbDatabaseSettings.setVerticalAlignment(javax.swing.SwingConstants.TOP); + + org.openide.awt.Mnemonics.setLocalizedText(bnTestDatabase, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnTestDatabase.text")); // NOI18N + bnTestDatabase.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnTestDatabaseActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(lbTestDatabase, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestDatabase.text")); // NOI18N + lbTestDatabase.setAutoscrolls(true); + + lbTestDbWarning.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(lbTestDbWarning, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.lbTestDbWarning.text")); // NOI18N + + javax.swing.GroupLayout pnDatabaseSettingsLayout = new javax.swing.GroupLayout(pnDatabaseSettings); + pnDatabaseSettings.setLayout(pnDatabaseSettingsLayout); + pnDatabaseSettingsLayout.setHorizontalGroup( + pnDatabaseSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pnDatabaseSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnDatabaseSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(tbDbHostname) + .addComponent(tbDbPort) + .addComponent(tbDbUsername) + .addComponent(tbDbPassword) + .addGroup(pnDatabaseSettingsLayout.createSequentialGroup() + .addComponent(lbDatabaseSettings) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(bnTestDatabase) + .addGap(18, 18, 18) + .addComponent(lbTestDatabase, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(pnDatabaseSettingsLayout.createSequentialGroup() + .addComponent(lbTestDbWarning) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + pnDatabaseSettingsLayout.setVerticalGroup( + pnDatabaseSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnDatabaseSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pnDatabaseSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbTestDatabase, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(pnDatabaseSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lbDatabaseSettings) + .addComponent(bnTestDatabase))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbDbHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbDbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbDbUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbDbPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lbTestDbWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + org.openide.awt.Mnemonics.setLocalizedText(cbEnableMultiUser, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.cbEnableMultiUser.text")); // NOI18N + cbEnableMultiUser.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + cbEnableMultiUserItemStateChanged(evt); + } + }); + + tbOops.setEditable(false); + tbOops.setForeground(new java.awt.Color(255, 0, 0)); + tbOops.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.tbOops.text")); // NOI18N + tbOops.setBorder(null); + tbOops.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tbOopsActionPerformed(evt); + } + }); + + multiUserRestartLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(multiUserRestartLabel, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.multiUserRestartLabel.text")); // NOI18N + + javax.swing.GroupLayout multiUserPanelLayout = new javax.swing.GroupLayout(multiUserPanel); + multiUserPanel.setLayout(multiUserPanelLayout); + multiUserPanelLayout.setHorizontalGroup( + multiUserPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(multiUserPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(multiUserPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pnDatabaseSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(multiUserPanelLayout.createSequentialGroup() + .addComponent(multiUserRestartLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 496, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 246, Short.MAX_VALUE)) + .addComponent(pnSolrSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pnMessagingSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(multiUserPanelLayout.createSequentialGroup() + .addComponent(cbEnableMultiUser) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tbOops))) + .addContainerGap()) + ); + multiUserPanelLayout.setVerticalGroup( + multiUserPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(multiUserPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(multiUserRestartLabel) + .addGap(15, 15, 15) + .addGroup(multiUserPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(cbEnableMultiUser) + .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pnDatabaseSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnSolrSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnMessagingSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + mainTabPane.addTab(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.multiUserPanel.TabConstraints.tabTitle"), multiUserPanel); // NOI18N + + nodePanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + + jPanelNodeType.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jPanelNodeType.border.title"))); // NOI18N + jPanelNodeType.setMinimumSize(new java.awt.Dimension(50, 50)); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectMode, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelSelectMode.text")); // NOI18N + + restartRequiredNodeLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(restartRequiredNodeLabel, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.restartRequiredNodeLabel.text")); // NOI18N + + modeRadioButtons.add(jRadioButtonStandalone); + org.openide.awt.Mnemonics.setLocalizedText(jRadioButtonStandalone, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonStandalone.text")); // NOI18N + jRadioButtonStandalone.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonStandalone.toolTipText")); // NOI18N + jRadioButtonStandalone.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jRadioButtonStandaloneActionPerformed(evt); + } + }); + + modeRadioButtons.add(jRadioButtonCopyFilesMode); + org.openide.awt.Mnemonics.setLocalizedText(jRadioButtonCopyFilesMode, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonCopyFilesMode.text")); // NOI18N + jRadioButtonCopyFilesMode.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jRadioButtonCopyFilesModeActionPerformed(evt); + } + }); + + modeRadioButtons.add(jRadioButtonAutomated); + org.openide.awt.Mnemonics.setLocalizedText(jRadioButtonAutomated, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonAutomated.text")); // NOI18N + jRadioButtonAutomated.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonAutomated.toolTipText")); // NOI18N + jRadioButtonAutomated.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jRadioButtonAutomatedActionPerformed(evt); + } + }); + + modeRadioButtons.add(jRadioButtonReview); + org.openide.awt.Mnemonics.setLocalizedText(jRadioButtonReview, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonReview.text")); // NOI18N + jRadioButtonReview.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jRadioButtonReview.toolTipText")); // NOI18N + jRadioButtonReview.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jRadioButtonReviewActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectInputFolder, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelSelectInputFolder.text")); // NOI18N + jLabelSelectInputFolder.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); + + inputPathTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.inputPathTextField.text")); // NOI18N + inputPathTextField.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.inputPathTextField.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseInputFolderButton, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.browseInputFolderButton.text")); // NOI18N + browseInputFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseInputFolderButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectOutputFolder, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelSelectOutputFolder.text")); // NOI18N + jLabelSelectOutputFolder.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); + + outputPathTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.outputPathTextField.text")); // NOI18N + outputPathTextField.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.outputPathTextField.toolTipText")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseOutputFolderButton, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.browseOutputFolderButton.text")); // NOI18N + browseOutputFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseOutputFolderButtonActionPerformed(evt); + } + }); + + jLabelVikingDiagram.setIcon(new javax.swing.ImageIcon(getClass().getResource("/viking/images/viking.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabelVikingDiagram, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelVikingDiagram.text")); // NOI18N + + jLabelInvalidImageFolder.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(jLabelInvalidImageFolder, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelInvalidImageFolder.text")); // NOI18N + + jLabelInvalidResultsFolder.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(jLabelInvalidResultsFolder, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelInvalidResultsFolder.text")); // NOI18N + + multiUserErrorTextField.setEditable(false); + multiUserErrorTextField.setForeground(new java.awt.Color(255, 0, 0)); + multiUserErrorTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.multiUserErrorTextField.text")); // NOI18N + multiUserErrorTextField.setBorder(null); + + javax.swing.GroupLayout jPanelNodeTypeLayout = new javax.swing.GroupLayout(jPanelNodeType); + jPanelNodeType.setLayout(jPanelNodeTypeLayout); + jPanelNodeTypeLayout.setHorizontalGroup( + jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(outputPathTextField, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(inputPathTextField, javax.swing.GroupLayout.Alignment.LEADING)) + .addGap(10, 10, 10) + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(browseInputFolderButton, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(browseOutputFolderButton, javax.swing.GroupLayout.Alignment.TRAILING))) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jRadioButtonCopyFilesMode) + .addComponent(jRadioButtonAutomated) + .addComponent(jRadioButtonReview) + .addComponent(jRadioButtonStandalone) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addComponent(jLabelSelectMode) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(restartRequiredNodeLabel)) + .addComponent(multiUserErrorTextField)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabelVikingDiagram)) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addComponent(jLabelSelectInputFolder) + .addGap(18, 18, 18) + .addComponent(jLabelInvalidImageFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 543, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addComponent(jLabelSelectOutputFolder) + .addGap(18, 18, 18) + .addComponent(jLabelInvalidResultsFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 544, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + jPanelNodeTypeLayout.setVerticalGroup( + jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelNodeTypeLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelSelectMode) + .addComponent(restartRequiredNodeLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jRadioButtonStandalone) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jRadioButtonCopyFilesMode) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jRadioButtonAutomated) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jRadioButtonReview) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(multiUserErrorTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jLabelVikingDiagram)) + .addGap(6, 6, 6) + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelSelectInputFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabelInvalidImageFolder)) + .addGap(1, 1, 1) + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(inputPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseInputFolderButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelSelectOutputFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabelInvalidResultsFolder)) + .addGap(1, 1, 1) + .addGroup(jPanelNodeTypeLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(browseOutputFolderButton) + .addComponent(outputPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(6, 6, 6)) + ); + + jPanelSharedConfig.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jPanelSharedConfig.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(sharedConfigCheckbox, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.sharedConfigCheckbox.text")); // NOI18N + sharedConfigCheckbox.setMaximumSize(new java.awt.Dimension(191, 14)); + sharedConfigCheckbox.setMinimumSize(new java.awt.Dimension(191, 14)); + sharedConfigCheckbox.setPreferredSize(new java.awt.Dimension(191, 14)); + sharedConfigCheckbox.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + sharedConfigCheckboxItemStateChanged(evt); + } + }); + + sharedSettingsTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.sharedSettingsTextField.text")); // NOI18N + sharedSettingsTextField.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(browseSharedSettingsButton, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.browseSharedSettingsButton.text")); // NOI18N + browseSharedSettingsButton.setEnabled(false); + browseSharedSettingsButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseSharedSettingsButtonActionPerformed(evt); + } + }); + + sharedSettingsErrorTextField.setEditable(false); + sharedSettingsErrorTextField.setForeground(new java.awt.Color(255, 0, 0)); + sharedSettingsErrorTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.sharedSettingsErrorTextField.text")); // NOI18N + sharedSettingsErrorTextField.setBorder(null); + + org.openide.awt.Mnemonics.setLocalizedText(masterNodeCheckBox, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.masterNodeCheckBox.text")); // NOI18N + masterNodeCheckBox.setEnabled(false); + masterNodeCheckBox.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + masterNodeCheckBoxItemStateChanged(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(uploadButton, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.uploadButton.text")); // NOI18N + uploadButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + uploadButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(downloadButton, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.downloadButton.text")); // NOI18N + downloadButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + downloadButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabelCurrentTask, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelCurrentTask.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabelTaskDescription, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jLabelTaskDescription.text")); // NOI18N + + configButtonErrorTextField.setEditable(false); + configButtonErrorTextField.setForeground(new java.awt.Color(255, 0, 0)); + configButtonErrorTextField.setText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.configButtonErrorTextField.text")); // NOI18N + configButtonErrorTextField.setBorder(null); + + javax.swing.GroupLayout jPanelSharedConfigLayout = new javax.swing.GroupLayout(jPanelSharedConfig); + jPanelSharedConfig.setLayout(jPanelSharedConfigLayout); + jPanelSharedConfigLayout.setHorizontalGroup( + jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelSharedConfigLayout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(jLabelCurrentTask) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jLabelTaskDescription, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + .addGroup(jPanelSharedConfigLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelSharedConfigLayout.createSequentialGroup() + .addComponent(sharedSettingsTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 400, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseSharedSettingsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(uploadButton, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanelSharedConfigLayout.createSequentialGroup() + .addComponent(downloadButton, javax.swing.GroupLayout.PREFERRED_SIZE, 143, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(configButtonErrorTextField)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanelSharedConfigLayout.createSequentialGroup() + .addComponent(sharedConfigCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(sharedSettingsErrorTextField)) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 692, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(pbTaskInProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 695, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(masterNodeCheckBox)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanelSharedConfigLayout.setVerticalGroup( + jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelSharedConfigLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(sharedConfigCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(sharedSettingsErrorTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(sharedSettingsTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseSharedSettingsButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(downloadButton) + .addComponent(configButtonErrorTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(6, 6, 6) + .addComponent(masterNodeCheckBox, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(uploadButton) + .addGap(8, 8, 8) + .addGroup(jPanelSharedConfigLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelCurrentTask) + .addComponent(jLabelTaskDescription)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(pbTaskInProgress, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + jPanelIngestSettings.setBorder(javax.swing.BorderFactory.createTitledBorder(javax.swing.BorderFactory.createEtchedBorder(), org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.jPanelIngestSettings.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnEditIngestSettings, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnEditIngestSettings.text")); // NOI18N + bnEditIngestSettings.setToolTipText(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnEditIngestSettings.toolTipText")); // NOI18N + bnEditIngestSettings.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnEditIngestSettingsActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnAdvancedSettings, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnAdvancedSettings.text")); // NOI18N + bnAdvancedSettings.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnAdvancedSettingsActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnFileExport, org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.bnFileExport.text")); // NOI18N + bnFileExport.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnFileExportActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanelIngestSettingsLayout = new javax.swing.GroupLayout(jPanelIngestSettings); + jPanelIngestSettings.setLayout(jPanelIngestSettingsLayout); + jPanelIngestSettingsLayout.setHorizontalGroup( + jPanelIngestSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelIngestSettingsLayout.createSequentialGroup() + .addContainerGap() + .addComponent(bnEditIngestSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnAdvancedSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnFileExport, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanelIngestSettingsLayout.setVerticalGroup( + jPanelIngestSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanelIngestSettingsLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanelIngestSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnEditIngestSettings) + .addComponent(bnFileExport) + .addComponent(bnAdvancedSettings)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout nodePanelLayout = new javax.swing.GroupLayout(nodePanel); + nodePanel.setLayout(nodePanelLayout); + nodePanelLayout.setHorizontalGroup( + nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(nodePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jPanelNodeType, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanelSharedConfig, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanelIngestSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + nodePanelLayout.setVerticalGroup( + nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(nodePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jPanelNodeType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jPanelIngestSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(jPanelSharedConfig, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + mainTabPane.addTab(org.openide.util.NbBundle.getMessage(OptionsPanel.class, "OptionsPanel.nodePanel.TabConstraints.tabTitle"), nodePanel); // NOI18N + + 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) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(mainTabPane, javax.swing.GroupLayout.PREFERRED_SIZE, 771, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(bnSave, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 90, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(15, 15, 15)))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(mainTabPane, javax.swing.GroupLayout.PREFERRED_SIZE, 705, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnSave) + .addComponent(bnCancel)) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed + parent.dispose(); + }//GEN-LAST:event_bnCancelActionPerformed + + private void bnEditIngestSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnEditIngestSettingsActionPerformed + displayIngestJobSettingsPanel(); + }//GEN-LAST:event_bnEditIngestSettingsActionPerformed + + private void browseSharedSettingsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseSharedSettingsButtonActionPerformed + + String oldText = sharedSettingsTextField.getText().trim(); + // set the current directory of the FileChooser if the oldText is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + fc.setDialogTitle("Select shared configuration folder:"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + sharedSettingsTextField.setText(path); + validateSettings(); + } + }//GEN-LAST:event_browseSharedSettingsButtonActionPerformed + + private void browseOutputFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseOutputFolderButtonActionPerformed + String oldText = outputPathTextField.getText().trim(); + // set the current directory of the FileChooser if the oldText is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + fc.setDialogTitle("Select case output folder:"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + outputPathTextField.setText(path); + validateSettings(); + } + }//GEN-LAST:event_browseOutputFolderButtonActionPerformed + + private void browseInputFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseInputFolderButtonActionPerformed + String oldText = inputPathTextField.getText().trim(); + // set the current directory of the FileChooser if the oldText is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + fc.setDialogTitle("Select case input folder:"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + inputPathTextField.setText(path); + validateSettings(); + } + }//GEN-LAST:event_browseInputFolderButtonActionPerformed + + private void jRadioButtonReviewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRadioButtonReviewActionPerformed + enableOptionsBasedOnMode(OptionsUiMode.REVIEW); + setSharedConfigEnable(); + validateSettings(); + }//GEN-LAST:event_jRadioButtonReviewActionPerformed + + private void jRadioButtonAutomatedActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRadioButtonAutomatedActionPerformed + enableOptionsBasedOnMode(OptionsUiMode.AIM); + setSharedConfigEnable(); + cbEnableMultiUserItemStateChanged(null); + validateSettings(); + }//GEN-LAST:event_jRadioButtonAutomatedActionPerformed + + private void jRadioButtonCopyFilesModeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRadioButtonCopyFilesModeActionPerformed + enableOptionsBasedOnMode(OptionsUiMode.UTILITY); + setSharedConfigEnable(); + validateSettings(); + }//GEN-LAST:event_jRadioButtonCopyFilesModeActionPerformed + + private void jRadioButtonStandaloneActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jRadioButtonStandaloneActionPerformed + enableOptionsBasedOnMode(OptionsUiMode.STANDALONE); + setSharedConfigEnable(); + validateSettings(); + }//GEN-LAST:event_jRadioButtonStandaloneActionPerformed + + boolean permissionsAppropriate(String path) { + return FileUtil.hasReadWriteAccess(Paths.get(path)); + } + + private void bnSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnSaveActionPerformed + boolean isValidNodePanel = true; + + switch (getModeFromRadioButtons()) { + case UTILITY: + if (!validateImagePath()) { + isValidNodePanel = false; + } + if (!validateResultsPath()) { + isValidNodePanel = false; + } + break; + case AIM: + if (!validateImagePath()) { + isValidNodePanel = false; + } + if (!validateResultsPath()) { + isValidNodePanel = false; + } + if (!validateSharedSettingsPath()) { + isValidNodePanel = false; + configButtonErrorTextField.setText("Shared configuration folder is invalid"); + } + break; + case REVIEW: + if (!validateResultsPath()) { + isValidNodePanel = false; + } + break; + + case STANDALONE: + break; + default: + break; + } + if (isValidNodePanel) { + store(); + parent.dispose(); + } + }//GEN-LAST:event_bnSaveActionPerformed + + private void cbEnableMultiUserItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cbEnableMultiUserItemStateChanged + if (!cbEnableMultiUser.isSelected()) { + tbOops.setText(""); + bnTestDatabase.setEnabled(false); + lbTestDatabase.setIcon(null); + bnTestSolr.setEnabled(false); + lbTestSolr.setIcon(null); + bnTestMessageService.setEnabled(false); + lbTestMessageService.setIcon(null); + lbTestDbWarning.setText(""); + lbTestSolrWarning.setText(""); + lbTestMessageWarning.setText(""); + lbSolrSettings.setEnabled(false); + enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); + } else if (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected() && !masterNodeCheckBox.isSelected()) { + bnTestDatabase.setEnabled(false); + lbTestDatabase.setIcon(null); + lbTestSolr.setIcon(null); + bnTestMessageService.setEnabled(false); + lbTestMessageService.setIcon(null); + lbTestDbWarning.setText(""); + lbTestSolrWarning.setText(""); + lbTestMessageWarning.setText(""); + enableMultiUserComponents(textBoxes, false); + bnTestSolr.setEnabled(true); + lbTestSolr.setEnabled(true); + tbSolrHostname.setEnabled(true); + tbSolrPort.setEnabled(true); + lbSolrSettings.setEnabled(true); + } else { + enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); + } + + validateSettings(); + }//GEN-LAST:event_cbEnableMultiUserItemStateChanged + + private void tbOopsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbOopsActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_tbOopsActionPerformed + + private void downloadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_downloadButtonActionPerformed + // First save the shared config folder and solr settings to the properties + String globalSettingsPath = getNormalizedFolderPath(sharedSettingsTextField.getText().trim()); + VikingUserPreferences.setSharedConfigFolder(globalSettingsPath); + UserPreferences.setIndexingServerHost(tbSolrHostname.getText().trim()); + UserPreferences.setIndexingServerPort(Integer.parseInt(tbSolrPort.getText().trim())); + + disableUI(); + jLabelCurrentTask.setEnabled(true); + jLabelTaskDescription.setEnabled(true); + pbTaskInProgress.setEnabled(true); + pbTaskInProgress.setIndeterminate(true); + + UpdateConfigSwingWorker worker = new UpdateConfigSwingWorker(ConfigTaskType.DOWNLOAD); + try { + worker.execute(); + } catch (Exception ex) { + jLabelTaskDescription.setText(ex.getLocalizedMessage()); + } + }//GEN-LAST:event_downloadButtonActionPerformed + + private void uploadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_uploadButtonActionPerformed + store(); + + disableUI(); + jLabelCurrentTask.setEnabled(true); + jLabelTaskDescription.setEnabled(true); + pbTaskInProgress.setEnabled(true); + pbTaskInProgress.setIndeterminate(true); + + UpdateConfigSwingWorker worker = new UpdateConfigSwingWorker(ConfigTaskType.UPLOAD); + try { + worker.execute(); + } catch (Exception ex) { + jLabelTaskDescription.setText(ex.getLocalizedMessage()); + } + }//GEN-LAST:event_uploadButtonActionPerformed + + private void sharedConfigCheckboxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_sharedConfigCheckboxItemStateChanged + // Enable the global settings text box and browse button iff the checkbox is checked and enabled + setSharedConfigEnable(); + }//GEN-LAST:event_sharedConfigCheckboxItemStateChanged + + private void setSharedConfigEnable() { + setEnabledStateForSharedConfiguration(); + if (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected() && jRadioButtonAutomated.isSelected()) { + sharedSettingsTextField.setEnabled(true); + browseSharedSettingsButton.setEnabled(true); + masterNodeCheckBox.setEnabled(true); + downloadButton.setEnabled(true); + validateSettings(); + } else { + sharedSettingsTextField.setEnabled(false); + browseSharedSettingsButton.setEnabled(false); + masterNodeCheckBox.setEnabled(false); + downloadButton.setEnabled(false); + sharedSettingsErrorTextField.setText(""); + validateSettings(); + } + cbEnableMultiUserItemStateChanged(null); + } + + private void masterNodeCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_masterNodeCheckBoxItemStateChanged + // Enable the global settings text box and browse button iff the checkbox is checked and enabled + setEnabledStateForSharedConfiguration(); + if (masterNodeCheckBox.isEnabled() && masterNodeCheckBox.isSelected()) { + uploadButton.setEnabled(true); + validateSettings(); // This will disable the upload/save button if the settings aren't currently valid + } else { + uploadButton.setEnabled(false); + } + if (sharedConfigCheckbox.isEnabled()) { + jRadioButtonAutomated.setEnabled(false); + jRadioButtonCopyFilesMode.setEnabled(false); + jRadioButtonReview.setEnabled(false); + jRadioButtonStandalone.setEnabled(false); + } + cbEnableMultiUserItemStateChanged(null); + }//GEN-LAST:event_masterNodeCheckBoxItemStateChanged + + private void bnTestDatabaseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestDatabaseActionPerformed + lbTestDatabase.setIcon(null); + lbTestDbWarning.setText(""); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + CaseDbConnectionInfo info = new CaseDbConnectionInfo( + this.tbDbHostname.getText().trim(), + this.tbDbPort.getText().trim(), + this.tbDbUsername.getText().trim(), + new String(this.tbDbPassword.getPassword()), + TskData.DbType.POSTGRESQL); + + SleuthkitCase.tryConnect(info); + lbTestDatabase.setIcon(goodIcon); + lbTestDbWarning.setText(""); + } catch (TskCoreException ex) { + lbTestDatabase.setIcon(badIcon); + lbTestDbWarning.setText(ex.getMessage()); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }//GEN-LAST:event_bnTestDatabaseActionPerformed + + private void bnTestSolrActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestSolrActionPerformed + lbTestSolr.setIcon(null); + lbTestSolrWarning.setText(""); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + KeywordSearchService kwsService = Lookup.getDefault().lookup(KeywordSearchService.class); + try { + if (kwsService != null) { + int port = Integer.parseInt(tbSolrPort.getText().trim()); + kwsService.tryConnect(tbSolrHostname.getText().trim(), port); + lbTestSolr.setIcon(goodIcon); + lbTestSolrWarning.setText(""); + } else { + lbTestSolr.setIcon(badIcon); + lbTestSolrWarning.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.KeywordSearchNull")); + } + } catch (NumberFormatException ex) { + lbTestSolr.setIcon(badIcon); + lbTestSolrWarning.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.InvalidPortNumber")); + } catch (KeywordSearchServiceException ex) { + lbTestSolr.setIcon(badIcon); + lbTestSolrWarning.setText(ex.getMessage()); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }//GEN-LAST:event_bnTestSolrActionPerformed + + private void bnTestMessageServiceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnTestMessageServiceActionPerformed + lbTestMessageService.setIcon(null); + lbTestMessageWarning.setText(""); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + int port; + try { + port = Integer.parseInt(this.tbMsgPort.getText().trim()); + } catch (NumberFormatException ex) { + lbTestMessageService.setIcon(badIcon); + lbTestMessageWarning.setText(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.InvalidPortNumber")); + return; + } + + MessageServiceConnectionInfo info = new MessageServiceConnectionInfo( + this.tbMsgHostname.getText().trim(), + port, + this.tbMsgUsername.getText().trim(), + new String(this.tbMsgPassword.getPassword())); + try { + info.tryConnect(); + lbTestMessageService.setIcon(goodIcon); + lbTestMessageWarning.setText(""); + } catch (MessageServiceException ex) { + lbTestMessageService.setIcon(badIcon); + lbTestMessageWarning.setText(ex.getMessage()); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }//GEN-LAST:event_bnTestMessageServiceActionPerformed + + private void bnAdvancedSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnAdvancedSettingsActionPerformed + AdvancedOptionsPanel advancedOptionsPanel = new AdvancedOptionsPanel(getModeFromRadioButtons()); + if (JOptionPane.showConfirmDialog(null, advancedOptionsPanel, + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.AdvancedOptionsPanel.Title"), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION) { + advancedOptionsPanel.store(); + } + }//GEN-LAST:event_bnAdvancedSettingsActionPerformed + + private void bnFileExportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnFileExportActionPerformed + JDialog jDialog = new JDialog(); + FileExporterSettingsPanel fileExporterSettingsPanel = new FileExporterSettingsPanel(jDialog); + jDialog.addWindowListener(new java.awt.event.WindowAdapter() { + @Override + public void windowClosing(java.awt.event.WindowEvent windowEvent) { + fileExporterSettingsPanel.store(); + } + }); + JScrollPane jScrollPane = new JScrollPane(fileExporterSettingsPanel); + jScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + jScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + jScrollPane.setMinimumSize(new Dimension(100, 100)); + jDialog.add(jScrollPane); + jDialog.setTitle(NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.FileExportRules.text")); + jDialog.setIconImage(ImageUtilities.loadImage("/viking/images/viking32.gif")); + jDialog.setModalityType(java.awt.Dialog.ModalityType.APPLICATION_MODAL); + jDialog.pack(); + jDialog.setLocationRelativeTo(this); + jDialog.setVisible(true); + }//GEN-LAST:event_bnFileExportActionPerformed + + private void disableUI() { + enableOptionsBasedOnMode(OptionsUiMode.DOWNLOADING_CONFIGURATION); + downloadButton.setEnabled(false); + uploadButton.setEnabled(false); + browseSharedSettingsButton.setEnabled(false); + sharedConfigCheckbox.setEnabled(false); + masterNodeCheckBox.setEnabled(false); + sharedSettingsTextField.setEnabled(false); + // Disable multi-user settings + cbEnableMultiUser.setEnabled(false); + enableMultiUserComponents(textBoxes, false); + bnTestSolr.setEnabled(false); + bnSave.setEnabled(false); + bnCancel.setEnabled(false); + } + + private void resetUI() { + enableOptionsBasedOnMode(getModeFromRadioButtons()); + setSharedConfigEnable(); + // Enable multi-user settings + cbEnableMultiUser.setEnabled(true); + enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); + bnTestSolr.setEnabled(true); + bnCancel.setEnabled(true); + + validateSettings(); // Will re-enable the save button if everything is valid + } + + public enum ConfigTaskType { + + DOWNLOAD, UPLOAD + } + + /** + * Handles running the upload configuration task. There's some added + * complexity here in order to re-enable the update button after the update + * completes. + * + */ + public class UpdateConfigSwingWorker extends SwingWorker { + + private String errorMessage = null; + private SharedConfiguration.SharedConfigResult uploadResult = SharedConfiguration.SharedConfigResult.SUCCESS; + private final SharedConfiguration sharedConfig = new SharedConfiguration(this); + private final ConfigTaskType task; + + protected UpdateConfigSwingWorker(ConfigTaskType task) { + this.task = task; + } + + @Override + protected Void doInBackground() throws InterruptedException { + try { + if (task == ConfigTaskType.UPLOAD) { + uploadResult = sharedConfig.uploadConfiguration(); + } else { + uploadResult = sharedConfig.downloadConfiguration(); + } + } catch (Exception ex) { + if (ex instanceof InterruptedException) { + throw (InterruptedException) ex; + } + errorMessage = ex.getLocalizedMessage(); + } + return null; + } + + public void publishStatus(String status) { + publish(status); + } + + @Override + protected void process(List messages) { + for (String status : messages) { + jLabelTaskDescription.setText(status); + } + } + + @Override + protected void done() { + // It would be nicer to hide the progress bar, but that seems to shrink the whole panel + pbTaskInProgress.setIndeterminate(false); + + if (uploadResult == SharedConfiguration.SharedConfigResult.LOCKED) { + jLabelTaskDescription.setText("Transfer of shared configuration incomplete"); + JOptionPane.showMessageDialog(null, "Shared configuration folder is currently locked by another node - try again in a few minutes", "Error", JOptionPane.ERROR_MESSAGE); + } else if (errorMessage != null) { + //MessageNotifyUtil.Message.info(errorMessage); + jLabelTaskDescription.setText("Transfer of shared configuration incomplete"); + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), errorMessage, "Error copying configuration", JOptionPane.ERROR_MESSAGE); + } else { + jLabelTaskDescription.setText("Shared configuration copied successfully"); + translationSettingsPanel.populateIpAddress(VikingUserPreferences.getMachineTranslationIpAddress()); // populate other panel's settings + bnSave.setEnabled(true); + } + + // Check if anything requiring a reset has changed and update the UI + if (isResetNeeded()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.MustRestart"), + NbBundle.getMessage(OptionsPanel.class, "VikingOptionsPanel.restartRequiredLabel.text"), + JOptionPane.WARNING_MESSAGE); + } + + // Re-enable everything here + resetUI(); + setEnabledStateForSharedConfiguration(); + pbTaskInProgress.setEnabled(false); + } + } + + void setEnabledStateForSharedConfiguration() { + if (jRadioButtonAutomated.isSelected()) { + if (sharedConfigCheckbox.isEnabled() && sharedConfigCheckbox.isSelected()) { + setEnabledState(masterNodeCheckBox.isSelected()); + } else { + // If we are in AIM mode and shared config is not enabled, allow this + setEnabledState(true); + } + } + } + + void setEnabledState(boolean enabled) { + translationSettingsPanel.setEnabled(enabled); + bnAdvancedSettings.setEnabled(enabled); + bnEditIngestSettings.setEnabled(enabled); + bnFileExport.setEnabled(enabled); + bnTestDatabase.setEnabled(enabled); + bnTestMessageService.setEnabled(enabled); + browseInputFolderButton.setEnabled(enabled); + browseOutputFolderButton.setEnabled(enabled); + browseSharedSettingsButton.setEnabled(sharedConfigCheckbox.isSelected() && jRadioButtonAutomated.isSelected()); + configButtonErrorTextField.setEnabled(enabled); + inputPathTextField.setEnabled(enabled); + jLabelInvalidImageFolder.setEnabled(enabled); + jLabelInvalidResultsFolder.setEnabled(enabled); + jLabelSelectInputFolder.setEnabled(enabled); + jLabelSelectMode.setEnabled(enabled); + jLabelSelectOutputFolder.setEnabled(enabled); + jPanelIngestSettings.setEnabled(enabled); + jPanelNodeType.setEnabled(enabled); + jPanelSharedConfig.setEnabled(enabled); + jRadioButtonAutomated.setEnabled(enabled); + jRadioButtonCopyFilesMode.setEnabled(enabled); + jRadioButtonReview.setEnabled(enabled); + jRadioButtonStandalone.setEnabled(enabled); + lbDatabaseSettings.setEnabled(enabled); + lbMessageServiceSettings.setEnabled(enabled); + lbTestDatabase.setEnabled(enabled); + lbTestDbWarning.setEnabled(enabled); + lbTestMessageService.setEnabled(enabled); + lbTestMessageWarning.setEnabled(enabled); + multiUserErrorTextField.setEnabled(enabled); + outputPathTextField.setEnabled(enabled); + pnDatabaseSettings.setEnabled(enabled); + pnMessagingSettings.setEnabled(enabled); + pnSolrSettings.setEnabled(enabled); + restartRequiredNodeLabel.setEnabled(enabled); + bnTestDatabase.setEnabled(enabled); + bnTestMessageService.setEnabled(enabled); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton bnAdvancedSettings; + private javax.swing.JButton bnCancel; + private javax.swing.JButton bnEditIngestSettings; + private javax.swing.JButton bnFileExport; + private javax.swing.JButton bnSave; + private javax.swing.JButton bnTestDatabase; + private javax.swing.JButton bnTestMessageService; + private javax.swing.JButton bnTestSolr; + private javax.swing.JButton browseInputFolderButton; + private javax.swing.JButton browseOutputFolderButton; + private javax.swing.JButton browseSharedSettingsButton; + private javax.swing.JCheckBox cbEnableMultiUser; + private javax.swing.JTextField configButtonErrorTextField; + private javax.swing.JButton downloadButton; + private javax.swing.JTextField inputPathTextField; + private javax.swing.JLabel jLabelCurrentTask; + private javax.swing.JLabel jLabelInvalidImageFolder; + private javax.swing.JLabel jLabelInvalidResultsFolder; + private javax.swing.JLabel jLabelSelectInputFolder; + private javax.swing.JLabel jLabelSelectMode; + private javax.swing.JLabel jLabelSelectOutputFolder; + private javax.swing.JLabel jLabelTaskDescription; + private javax.swing.JLabel jLabelVikingDiagram; + private javax.swing.JPanel jPanelIngestSettings; + private javax.swing.JPanel jPanelNodeType; + private javax.swing.JPanel jPanelSharedConfig; + private javax.swing.JRadioButton jRadioButtonAutomated; + private javax.swing.JRadioButton jRadioButtonCopyFilesMode; + private javax.swing.JRadioButton jRadioButtonReview; + private javax.swing.JRadioButton jRadioButtonStandalone; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JLabel lbDatabaseSettings; + private javax.swing.JLabel lbMessageServiceSettings; + private javax.swing.JLabel lbSolrSettings; + private javax.swing.JLabel lbTestDatabase; + private javax.swing.JLabel lbTestDbWarning; + private javax.swing.JLabel lbTestMessageService; + private javax.swing.JLabel lbTestMessageWarning; + private javax.swing.JLabel lbTestSolr; + private javax.swing.JLabel lbTestSolrWarning; + private javax.swing.JTabbedPane mainTabPane; + private javax.swing.JCheckBox masterNodeCheckBox; + private javax.swing.ButtonGroup modeRadioButtons; + private javax.swing.JTextField multiUserErrorTextField; + private javax.swing.JPanel multiUserPanel; + private javax.swing.JLabel multiUserRestartLabel; + private javax.swing.JPanel nodePanel; + private javax.swing.JTextField outputPathTextField; + private javax.swing.JProgressBar pbTaskInProgress; + private javax.swing.JPanel pnDatabaseSettings; + private javax.swing.JPanel pnMessagingSettings; + private javax.swing.JPanel pnSolrSettings; + private javax.swing.JLabel restartRequiredNodeLabel; + private javax.swing.JCheckBox sharedConfigCheckbox; + private javax.swing.JTextField sharedSettingsErrorTextField; + private javax.swing.JTextField sharedSettingsTextField; + private javax.swing.JTextField tbDbHostname; + private javax.swing.JPasswordField tbDbPassword; + private javax.swing.JTextField tbDbPort; + private javax.swing.JTextField tbDbUsername; + private javax.swing.JTextField tbMsgHostname; + private javax.swing.JPasswordField tbMsgPassword; + private javax.swing.JTextField tbMsgPort; + private javax.swing.JTextField tbMsgUsername; + private javax.swing.JTextField tbOops; + private javax.swing.JTextField tbSolrHostname; + private javax.swing.JTextField tbSolrPort; + private javax.swing.JButton uploadButton; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/configuration/SharedConfiguration.java b/Core/src/org/sleuthkit/autopsy/configuration/SharedConfiguration.java new file mode 100644 index 0000000000..8f0288124f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/configuration/SharedConfiguration.java @@ -0,0 +1,1231 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.configuration; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.util.zip.CRC32; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; +import java.util.prefs.BackingStoreException; +import org.apache.commons.io.FileUtils; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.keywordsearch.KeywordListsManager; +import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.core.ServicesMonitor; +import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; +import org.sleuthkit.autopsy.configuration.OptionsPanel.UpdateConfigSwingWorker; +import viking.application.CoordinationService; +import viking.application.CoordinationService.Lock; +import viking.application.CoordinationService.CoordinationServiceException; + +/* + * A utility class for loading and saving shared configuration data + */ +public class SharedConfiguration { + + // Files + private static final String AUTO_MODE_CONTEXT_FILE = "AutoModeContext.properties"; //NON-NLS + private static final String USER_DEFINED_TYPE_DEFINITIONS_FILE = "UserFileTypeDefinitions.settings"; //NON-NLS + private static final String USER_DEFINED_TYPE_DEFINITIONS_FILE_LEGACY = "UserFileTypeDefinitions.xml"; //NON-NLS + private static final String INTERESTING_FILES_SET_DEFS_FILE = "InterestingFileSets.settings"; //NON-NLS + private static final String INTERESTING_FILES_SET_DEFS_FILE_LEGACY = "InterestingFilesSetDefs.xml"; //NON-NLS + private static final String KEYWORD_SEARCH_SETTINGS = "keywords.settings"; //NON-NLS + private static final String KEYWORD_SEARCH_SETTINGS_LEGACY = "keywords.xml"; //NON-NLS + private static final String KEYWORD_SEARCH_GENERAL_LEGACY = "KeywordSearch.properties"; //NON-NLS + private static final String KEYWORD_SEARCH_NSRL_LEGACY = "KeywordSearch_NSRL.properties"; //NON-NLS + private static final String KEYWORD_SEARCH_OPTIONS_LEGACY = "KeywordSearch_Options.properties"; //NON-NLS + private static final String KEYWORD_SEARCH_SCRIPTS_LEGACY = "KeywordSearch_Scripts.properties"; //NON-NLS + private static final String FILE_EXT_MISMATCH_SETTINGS = "mismatch_config.settings"; //NON-NLS + private static final String FILE_EXT_MISMATCH_SETTINGS_LEGACY = "mismatch_config.xml"; //NON-NLS + private static final String ANDROID_TRIAGE = "AndroidTriage_Options.properties"; //NON-NLS + private static final String VIKING_PROPERTIES = "viking.properties"; //NON-NLS + private static final String GENERAL_PROPERTIES = "core.properties"; //NON-NLS + private static final String HASHDB_CONFIG_FILE_NAME = "hashLookup.settings"; //NON-NLS + private static final String HASHDB_CONFIG_FILE_NAME_LEGACY = "hashsets.xml"; //NON-NLS + public static final String FILE_EXPORTER_SETTINGS_FILE = "fileexporter.settings"; //NON-NLS + private static final String SHARED_CONFIG_VERSIONS = "SharedConfigVersions.txt"; //NON-NLS + + // Folders + private static final String AUTO_MODE_FOLDER = "AutoModeContext"; //NON-NLS + private static final String REMOTE_HASH_FOLDER = "hashDb"; //NON-NLS + private static final String PREFERENCES_FOLDER = "Preferences"; //NON-NLS + public static final String FILE_EXPORTER_FOLDER = "Automated File Exporter"; //NON-NLS + + private static final String LOCK_ROOT = "/viking"; // NON-NLS + private static final String UPLOAD_IN_PROGRESS_FILE = "uploadInProgress"; // NON-NLS + private static final String moduleDirPath = PlatformUtil.getUserConfigDirectory(); + private static final Logger logger = Logger.getLogger(SharedConfiguration.class.getName()); + + private final UpdateConfigSwingWorker swingWorker; + private VikingUserPreferences.SelectedVikingMode vikingMode; + private String sharedConfigFolder; + private int fileIngestThreads; + private boolean sharedConfigMaster; + private boolean showToolsWarning; + private boolean displayLocalTime; + private boolean hideKnownFilesInDataSource; + private boolean hideKnownFilesInViews; + private boolean keepPreferredViewer; + + /** + * Exception type thrown by shared configuration. + */ + public final static class SharedConfigurationException extends Exception { + + private static final long serialVersionUID = 1L; + + private SharedConfigurationException(String message) { + super(message); + } + + private SharedConfigurationException(String message, Throwable cause) { + super(message, cause); + } + } + + // Using this so we can indicate whether a read/write failed because the lock file is present, + // which we need to know to display the correct message in the auto-ingest dashboard about why + // processing has been paused. + public enum SharedConfigResult { + + SUCCESS, LOCKED + } + + public SharedConfiguration() { + swingWorker = null; + } + + /** + * Construct with a SwingWorker reference to allow status update messages to + * go to the GUI + * + * @param worker + */ + public SharedConfiguration(UpdateConfigSwingWorker worker) { + this.swingWorker = worker; + } + + private void publishTask(String task) { + if (swingWorker != null) { + swingWorker.publishStatus(task); + } + } + + /** + * Upload the current multi-user ingest settings to a shared folder. + * + * @throws SharedConfigurationException + * @throws CoordinationServiceException + * @throws InterruptedException + */ + public SharedConfigResult uploadConfiguration() throws SharedConfigurationException, CoordinationServiceException, InterruptedException { + publishTask("Starting shared configuration upload"); + + File remoteFolder = getSharedFolder(); + + try (Lock writeLock = CoordinationService.getInstance(LOCK_ROOT).tryGetExclusiveLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { + if (writeLock == null) { + logger.log(Level.INFO, String.format("Failed to lock %s - another node is currently uploading or downloading configuration", remoteFolder.getAbsolutePath())); + return SharedConfigResult.LOCKED; + } + + // Make sure the local settings have been initialized + if (!isConfigFolderPopulated(new File(moduleDirPath), false)) { + logger.log(Level.INFO, "Local configuration has not been initialized."); + throw new SharedConfigurationException("Local configuration has not been initialized. Please verify/update and save the settings using the Ingest Module Settings button and then retry the upload."); + } + + // Write a file to indicate that uploading is in progress. If we crash or + // have an error, this file will remain in the shared folder. + File uploadInProgress = new File(remoteFolder, UPLOAD_IN_PROGRESS_FILE); + if (!uploadInProgress.exists()) { + try { + Files.createFile(uploadInProgress.toPath()); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to create %s", uploadInProgress.toPath()), ex); + } + } + + // Make sure all recent changes are saved to the preference file + // Current testing suggests that we do not need to do this for the ingest settings + // because there is a longer delay between setting them and copying the files. + try { + // Make sure all recent changes are saved to the preference file + // Current testing suggests that we do not need to do this for the ingest settings + // because there is a longer delay between setting them and copying the files. + UserPreferences.saveToStorage(); + VikingUserPreferences.saveToStorage(); + } catch (BackingStoreException ex) { + throw new SharedConfigurationException("Failed to save shared configuration settings", ex); + } + + uploadAutoModeContextSettings(remoteFolder); + uploadEnabledModulesSettings(remoteFolder); + uploadFileTypeSettings(remoteFolder); + uploadInterestingFilesSettings(remoteFolder); + uploadKeywordSearchSettings(remoteFolder); + uploadFileExtMismatchSettings(remoteFolder); + uploadAndroidTriageSettings(remoteFolder); + uploadVikingSettings(remoteFolder); + uploadMultiUserAndGeneralSettings(remoteFolder); + uploadHashDbSettings(remoteFolder); + uploadFileExporterSettings(remoteFolder); + + try { + Files.deleteIfExists(uploadInProgress.toPath()); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to delete %s", uploadInProgress.toPath()), ex); + } + } + + return SharedConfigResult.SUCCESS; + } + + /** + * Download the multi-user settings from a shared folder. + * + * @throws SharedConfigurationException + * @throws InterruptedException + * @throws CoordinationServiceException + */ + public synchronized SharedConfigResult downloadConfiguration() throws SharedConfigurationException, InterruptedException, CoordinationServiceException { + publishTask("Starting shared configuration download"); + + // Save local settings that should not get overwritten + saveNonSharedSettings(); + + File remoteFolder = getSharedFolder(); + + try (Lock readLock = CoordinationService.getInstance(LOCK_ROOT).tryGetSharedLock(CoordinationService.CategoryNode.CONFIG, remoteFolder.getAbsolutePath(), 30, TimeUnit.MINUTES)) { + if (readLock == null) { + return SharedConfigResult.LOCKED; + } + + // Make sure the shared configuration was last uploaded successfully + File uploadInProgress = new File(remoteFolder, UPLOAD_IN_PROGRESS_FILE); + if (uploadInProgress.exists()) { + logger.log(Level.INFO, String.format("Shared configuration folder %s is corrupt - re-upload configuration", remoteFolder.getAbsolutePath())); + throw new SharedConfigurationException(String.format("Shared configuration folder %s is corrupt - re-upload configuration", remoteFolder.getAbsolutePath())); + } + + // Make sure the shared configuration folder isn't empty + if (!isConfigFolderPopulated(remoteFolder, true)) { + logger.log(Level.INFO, String.format("Shared configuration folder %s is missing files / may be empty. Aborting download.", remoteFolder.getAbsolutePath())); + throw new SharedConfigurationException(String.format("Shared configuration folder %s is missing files / may be empty. Aborting download.", remoteFolder.getAbsolutePath())); + } + + try { + /* Make sure all recent changes are saved to the preference file. + This also releases open file handles to the preference files. If this + is not done, then occasionally downloading of shared configuration + fails silently, likely because Java/OS is still holding the file handle. + The problem manifests itself by some of the old/original configuration files + sticking around after shared configuration has seemingly been successfully + updated. */ + UserPreferences.saveToStorage(); + VikingUserPreferences.saveToStorage(); + } catch (BackingStoreException ex) { + throw new SharedConfigurationException("Failed to save shared configuration settings", ex); + } + + downloadAutoModeContextSettings(remoteFolder); + downloadEnabledModuleSettings(remoteFolder); + downloadFileTypeSettings(remoteFolder); + downloadInterestingFilesSettings(remoteFolder); + downloadKeywordSearchSettings(remoteFolder); + downloadFileExtMismatchSettings(remoteFolder); + downloadAndroidTriageSettings(remoteFolder); + downloadFileExporterSettings(remoteFolder); + + // Download the Viking and general settings, then restore the current + // values for the unshared fields + downloadVikingSettings(remoteFolder); + downloadMultiUserAndGeneralSettings(remoteFolder); + try { + UserPreferences.reloadFromStorage(); + VikingUserPreferences.reloadFromStorage(); + } catch (BackingStoreException ex) { + throw new SharedConfigurationException("Failed to read shared configuration settings", ex); + } + + restoreNonSharedSettings(); + downloadHashDbSettings(remoteFolder); + } + + // Check Solr service + if (!isServiceUp(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString())) { + throw new SharedConfigurationException("Keyword search service is down"); + } + + // Check PostgreSQL service + if (!isServiceUp(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString())) { + throw new SharedConfigurationException("Case database server is down"); + } + + // Check ActiveMQ service + if (!isServiceUp(ServicesMonitor.Service.MESSAGING.toString())) { + throw new SharedConfigurationException("Messaging service is down"); + } + + // Check input folder permissions + String inputFolder = VikingUserPreferences.getAutoModeImageFolder(); + if (!FileUtil.hasReadWriteAccess(Paths.get(inputFolder))) { + throw new SharedConfigurationException("Cannot read input folder " + inputFolder + ". Check that the folder exists and that you have permissions to access it."); + } + + // Check output folder permissions + String outputFolder = VikingUserPreferences.getAutoModeResultsFolder(); + if (!FileUtil.hasReadWriteAccess(Paths.get(outputFolder))) { + throw new SharedConfigurationException("Cannot read output folder " + outputFolder + ". Check that the folder exists and that you have permissions to access it."); + } + + return SharedConfigResult.SUCCESS; + } + + /** + * Tests service of interest to verify that it is running. + * + * @param serviceName Name of the service. + * + * @return True if the service is running, false otherwise. + */ + private boolean isServiceUp(String serviceName) { + try { + return (ServicesMonitor.getInstance().getServiceStatus(serviceName).equals(ServicesMonitor.ServiceStatus.UP.toString())); + } catch (ServicesMonitor.ServicesMonitorException ex) { + logger.log(Level.SEVERE, String.format("Problem checking service status for %s", serviceName), ex); + return false; + } + } + + /** + * Save any settings that should not be overwritten by the shared + * configuration. + */ + private void saveNonSharedSettings() { + sharedConfigMaster = VikingUserPreferences.getSharedConfigMaster(); + sharedConfigFolder = VikingUserPreferences.getSharedConfigFolder(); + showToolsWarning = VikingUserPreferences.getShowToolsWarning(); + vikingMode = VikingUserPreferences.getVikingMode(); + displayLocalTime = UserPreferences.displayTimesInLocalTime(); + hideKnownFilesInDataSource = UserPreferences.hideKnownFilesInDataSourcesTree(); + hideKnownFilesInViews = UserPreferences.hideKnownFilesInViewsTree(); + keepPreferredViewer = UserPreferences.keepPreferredContentViewer(); + fileIngestThreads = UserPreferences.numberOfFileIngestThreads(); + } + + /** + * Restore the settings that may have been overwritten. + */ + private void restoreNonSharedSettings() { + VikingUserPreferences.setSharedConfigFolder(sharedConfigFolder); + VikingUserPreferences.setSharedConfigMaster(sharedConfigMaster); + VikingUserPreferences.setShowToolsWarning(showToolsWarning); + VikingUserPreferences.setVikingMode(vikingMode); + UserPreferences.setDisplayTimesInLocalTime(displayLocalTime); + UserPreferences.setHideKnownFilesInDataSourcesTree(hideKnownFilesInDataSource); + UserPreferences.setHideKnownFilesInViewsTree(hideKnownFilesInViews); + UserPreferences.setKeepPreferredContentViewer(keepPreferredViewer); + UserPreferences.setNumberOfFileIngestThreads(fileIngestThreads); + } + + /** + * Get the base folder being used to store the shared config settings. + * + * @return The shared configuration folder + * + * @throws SharedConfigurationException + */ + private static File getSharedFolder() throws SharedConfigurationException { + // Check that the shared folder is set and exists + String remoteConfigFolderPath = VikingUserPreferences.getSharedConfigFolder(); + if (remoteConfigFolderPath.isEmpty()) { + logger.log(Level.SEVERE, "Shared configuration folder is not set."); + throw new SharedConfigurationException("Shared configuration folder is not set."); + } + File remoteFolder = new File(remoteConfigFolderPath); + if (!remoteFolder.exists()) { + logger.log(Level.SEVERE, "Shared configuration folder {0} does not exist", remoteConfigFolderPath); + throw new SharedConfigurationException("Shared configuration folder " + remoteConfigFolderPath + " does not exist"); + } + return remoteFolder; + } + + /** + * Do a basic check to determine whether settings have been stored to the + * given folder. There may still be missing files/errors, but this will stop + * empty/corrupt settings from overwriting local settings or being uploaded. + * + * Currently we check for: - A non-empty AutoModeContext folder - An + * AutoModeContext properties file + * + * @param folder Folder to check the contents of + * @param isSharedFolder True if the folder being tested is the shared + * folder, false if its the local folder + * + * @return true if the folder appears to have been initialized, false + * otherwise + */ + private static boolean isConfigFolderPopulated(File folder, boolean isSharedFolder) { + + if (!folder.exists()) { + return false; + } + + // Check that the context directory exists and is not empty + File contextDir; + if (isSharedFolder) { + contextDir = new File(folder, AUTO_MODE_FOLDER); + } else { + IngestJobSettings ingestJobSettings = new IngestJobSettings(VikingUserPreferences.getAutoModeIngestModuleContextString()); + contextDir = ingestJobSettings.getSavedModuleSettingsFolder().toFile(); + } + + if ((!contextDir.exists()) || (!contextDir.isDirectory())) { + return false; + } + if (contextDir.listFiles().length == 0) { + return false; + } + + // Check that the automode context properties file exists + File contextProperties = new File(folder, AUTO_MODE_CONTEXT_FILE); + return contextProperties.exists(); + } + + /** + * Copy a local settings file to the remote folder. + * + * @param fileName Name of the file to copy + * @param localFolder Local settings folder + * @param remoteFolder Shared settings folder + * @param missingFileOk True if it's not an error if the source file is not + * found + * + * @throws SharedConfigurationException + */ + private static void copyToRemoteFolder(String fileName, String localFolder, File remoteFolder, boolean missingFileOk) throws SharedConfigurationException { + logger.log(Level.INFO, "Uploading {0} to {1}", new Object[]{fileName, remoteFolder.getAbsolutePath()}); + File localFile = new File(localFolder, fileName); + if (!localFile.exists()) { + Path deleteRemote = Paths.get(remoteFolder.toString(), fileName); + try { + if (deleteRemote.toFile().exists()) { + deleteRemote.toFile().delete(); + } + } catch (SecurityException ex) { + logger.log(Level.SEVERE, "Shared configuration {0} does not exist on local node, but unable to remove remote copy", fileName); + throw new SharedConfigurationException("Shared configuration file " + deleteRemote.toString() + " could not be deleted."); + } + if (!missingFileOk) { + logger.log(Level.SEVERE, "Local configuration file {0} does not exist", localFile.getAbsolutePath()); + throw new SharedConfigurationException("Local configuration file " + localFile.getAbsolutePath() + " does not exist"); + } else { + logger.log(Level.INFO, "Local configuration file {0} does not exist", localFile.getAbsolutePath()); + return; + } + } + + try { + FileUtils.copyFileToDirectory(localFile, remoteFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", localFile.getAbsolutePath(), remoteFolder.getAbsolutePath()), ex); + } + } + + /** + * Copy a shared settings file to the local settings folder. + * + * @param fileName Name of the file to copy + * @param localFolder Local settings folder + * @param remoteFolder Shared settings folder + * @param missingFileOk True if it's not an error if the source file is not + * found + * + * @throws SharedConfigurationException + */ + private static void copyToLocalFolder(String fileName, String localFolder, File remoteFolder, boolean missingFileOk) throws SharedConfigurationException { + logger.log(Level.INFO, "Downloading {0} from {1}", new Object[]{fileName, remoteFolder.getAbsolutePath()}); + + File remoteFile = new File(remoteFolder, fileName); + if (!remoteFile.exists()) { + Path deleteLocal = Paths.get(localFolder, fileName); + try { + if (deleteLocal.toFile().exists()) { + deleteLocal.toFile().delete(); + } + } catch (SecurityException ex) { + logger.log(Level.SEVERE, "Shared configuration {0} does not exist on remote node, but unable to remove local copy", fileName); + throw new SharedConfigurationException("Shared configuration file " + deleteLocal.toString() + " could not be deleted."); + } + if (!missingFileOk) { + logger.log(Level.SEVERE, "Shared configuration file {0} does not exist", remoteFile.getAbsolutePath()); + throw new SharedConfigurationException("Shared configuration file " + remoteFile.getAbsolutePath() + " does not exist"); + } else { + logger.log(Level.INFO, "Shared configuration file {0} does not exist", remoteFile.getAbsolutePath()); + return; + } + } + + File localSettingsFolder = new File(localFolder); + try { + FileUtils.copyFileToDirectory(remoteFile, localSettingsFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", remoteFile.getAbsolutePath(), localSettingsFolder.getAbsolutePath()), ex); + } + } + + /** + * Upload the basic set of auto-ingest settings to the shared folder. + * + * @param remoteFolder Shared settings folder + * + * @throws Exception + */ + private void uploadAutoModeContextSettings(File remoteFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Uploading shared configuration to {0}", remoteFolder.getAbsolutePath()); + publishTask("Uploading AutoModeContext configuration files"); + + // Make a subfolder + File remoteAutoConfFolder = new File(remoteFolder, AUTO_MODE_FOLDER); + try { + if (remoteAutoConfFolder.exists()) { + FileUtils.deleteDirectory(remoteAutoConfFolder); + } + Files.createDirectories(remoteAutoConfFolder.toPath()); + } catch (IOException | SecurityException ex) { + logger.log(Level.SEVERE, "Failed to create clean shared configuration subfolder " + remoteAutoConfFolder.getAbsolutePath(), ex); //NON-NLS + throw new SharedConfigurationException("Failed to create clean shared configuration subfolder " + remoteAutoConfFolder.getAbsolutePath()); + } + + IngestJobSettings ingestJobSettings = new IngestJobSettings(VikingUserPreferences.getAutoModeIngestModuleContextString()); + File localFolder = ingestJobSettings.getSavedModuleSettingsFolder().toFile(); + + if (!localFolder.exists()) { + logger.log(Level.SEVERE, "Local configuration folder {0} does not exist", localFolder.getAbsolutePath()); + throw new SharedConfigurationException("Local configuration folder " + localFolder.getAbsolutePath() + " does not exist"); + } + + try { + FileUtils.copyDirectory(localFolder, remoteAutoConfFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", localFolder.getAbsolutePath(), remoteAutoConfFolder.getAbsolutePath()), ex); + } + } + + /** + * Download the basic set of auto-ingest settings from the shared folder + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadAutoModeContextSettings(File remoteFolder) throws SharedConfigurationException { + logger.log(Level.INFO, "Downloading shared configuration from {0}", remoteFolder.getAbsolutePath()); + publishTask("Downloading AutoModeContext configuration files"); + + // Check that the remote subfolder exists + File remoteAutoConfFolder = new File(remoteFolder, AUTO_MODE_FOLDER); + if (!remoteAutoConfFolder.exists()) { + logger.log(Level.SEVERE, "Shared configuration folder {0} does not exist", remoteAutoConfFolder.getAbsolutePath()); + throw new SharedConfigurationException("Shared configuration folder " + remoteAutoConfFolder.getAbsolutePath() + " does not exist"); + } + + // Get/create the local subfolder + IngestJobSettings ingestJobSettings = new IngestJobSettings(VikingUserPreferences.getAutoModeIngestModuleContextString()); + File localFolder = ingestJobSettings.getSavedModuleSettingsFolder().toFile(); + + try { + if (localFolder.exists()) { + FileUtils.deleteDirectory(localFolder); + } + Files.createDirectories(localFolder.toPath()); + } catch (IOException | SecurityException ex) { + logger.log(Level.SEVERE, "Failed to create clean local configuration folder " + localFolder.getAbsolutePath(), ex); //NON-NLS + throw new SharedConfigurationException("Failed to create clean local configuration folder " + localFolder.getAbsolutePath()); + } + + try { + FileUtils.copyDirectory(remoteAutoConfFolder, localFolder); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", remoteFolder.getAbsolutePath(), localFolder.getAbsolutePath()), ex); + } + } + + /** + * Upload settings file containing enabled ingest modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadEnabledModulesSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading enabled module configuration"); + copyToRemoteFolder(AUTO_MODE_CONTEXT_FILE, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings file containing enabled ingest modules. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadEnabledModuleSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading enabled module configuration"); + copyToLocalFolder(AUTO_MODE_CONTEXT_FILE, moduleDirPath, remoteFolder, true); + } + + /** + * Upload settings file containing file type settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadFileTypeSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading FileType module configuration"); + copyToRemoteFolder(USER_DEFINED_TYPE_DEFINITIONS_FILE, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(USER_DEFINED_TYPE_DEFINITIONS_FILE_LEGACY, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings file containing file type settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadFileTypeSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading FileType module configuration"); + copyToLocalFolder(USER_DEFINED_TYPE_DEFINITIONS_FILE, moduleDirPath, remoteFolder, true); + copyToLocalFolder(USER_DEFINED_TYPE_DEFINITIONS_FILE_LEGACY, moduleDirPath, remoteFolder, true); + } + + /** + * Upload settings for the interesting files module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadInterestingFilesSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading InterestingFiles module configuration"); + copyToRemoteFolder(INTERESTING_FILES_SET_DEFS_FILE_LEGACY, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(INTERESTING_FILES_SET_DEFS_FILE, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings for the interesting files module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadInterestingFilesSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading InterestingFiles module configuration"); + copyToLocalFolder(INTERESTING_FILES_SET_DEFS_FILE_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(INTERESTING_FILES_SET_DEFS_FILE, moduleDirPath, remoteFolder, true); + } + + /** + * Upload settings for the keyword search module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadKeywordSearchSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading KeywordSearch module configuration"); + copyToRemoteFolder(KEYWORD_SEARCH_SETTINGS, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(KEYWORD_SEARCH_SETTINGS_LEGACY, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(KEYWORD_SEARCH_GENERAL_LEGACY, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(KEYWORD_SEARCH_NSRL_LEGACY, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(KEYWORD_SEARCH_OPTIONS_LEGACY, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(KEYWORD_SEARCH_SCRIPTS_LEGACY, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings for the keyword search module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadKeywordSearchSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading KeywordSearch module configuration"); + copyToLocalFolder(KEYWORD_SEARCH_SETTINGS, moduleDirPath, remoteFolder, true); + copyToLocalFolder(KEYWORD_SEARCH_SETTINGS_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(KEYWORD_SEARCH_GENERAL_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(KEYWORD_SEARCH_NSRL_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(KEYWORD_SEARCH_OPTIONS_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(KEYWORD_SEARCH_SCRIPTS_LEGACY, moduleDirPath, remoteFolder, true); + KeywordListsManager.reloadKeywordLists(); + } + + /** + * Upload settings for the file extension mismatch module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadFileExtMismatchSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading File Extension Mismatch module configuration"); + copyToRemoteFolder(FILE_EXT_MISMATCH_SETTINGS, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(FILE_EXT_MISMATCH_SETTINGS_LEGACY, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings for the file extension mismatch module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadFileExtMismatchSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading File Extension Mismatch module configuration"); + copyToLocalFolder(FILE_EXT_MISMATCH_SETTINGS, moduleDirPath, remoteFolder, true); + copyToLocalFolder(FILE_EXT_MISMATCH_SETTINGS_LEGACY, moduleDirPath, remoteFolder, true); + } + + /** + * Upload settings for the android triage module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadAndroidTriageSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading Android Triage module configuration"); + copyToRemoteFolder(ANDROID_TRIAGE, moduleDirPath, remoteFolder, true); + } + + /** + * Download settings for the android triage module. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadAndroidTriageSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading Android Triage module configuration"); + copyToLocalFolder(ANDROID_TRIAGE, moduleDirPath, remoteFolder, true); + } + + /** + * Upload Viking settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadVikingSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading Viking configuration"); + File vikingFolder = new File(moduleDirPath, PREFERENCES_FOLDER); + copyToRemoteFolder(VIKING_PROPERTIES, vikingFolder.getAbsolutePath(), remoteFolder, false); + } + + /** + * Download Viking settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadVikingSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading Viking configuration"); + File vikingFolder = new File(moduleDirPath, PREFERENCES_FOLDER); + copyToLocalFolder(VIKING_PROPERTIES, vikingFolder.getAbsolutePath(), remoteFolder, false); + } + + /** + * Upload File Exporter settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadFileExporterSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading File Exporter configuration"); + File fileExporterFolder = new File(moduleDirPath, FILE_EXPORTER_FOLDER); + copyToRemoteFolder(FILE_EXPORTER_SETTINGS_FILE, fileExporterFolder.getAbsolutePath(), remoteFolder, true); + } + + /** + * Download File Exporter settings. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadFileExporterSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading File Exporter configuration"); + File fileExporterFolder = new File(moduleDirPath, FILE_EXPORTER_FOLDER); + copyToLocalFolder(FILE_EXPORTER_SETTINGS_FILE, fileExporterFolder.getAbsolutePath(), remoteFolder, true); + } + + /** + * Upload multi-user settings and other general Autopsy settings + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadMultiUserAndGeneralSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading multi user configuration"); + File generalSettingsFolder = Paths.get(moduleDirPath, PREFERENCES_FOLDER, "org", "sleuthkit", "autopsy").toFile(); + copyToRemoteFolder(GENERAL_PROPERTIES, generalSettingsFolder.getAbsolutePath(), remoteFolder, false); + } + + /** + * Download multi-user settings and other general Autopsy settings + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadMultiUserAndGeneralSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading multi user configuration"); + File generalSettingsFolder = Paths.get(moduleDirPath, PREFERENCES_FOLDER, "org", "sleuthkit", "autopsy").toFile(); + copyToLocalFolder(GENERAL_PROPERTIES, generalSettingsFolder.getAbsolutePath(), remoteFolder, false); + } + + /** + * Upload settings and hash databases to the shared folder. The general + * algorithm is: - Copy the general settings in hashsets.xml - For each hash + * database listed in hashsets.xml: - Calculate the CRC of the database - If + * the CRC does not match the one listed for that database in the shared + * folder, (or if no entry exists), copy the database - Store the CRCs for + * each database in the shared folder and locally + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void uploadHashDbSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Uploading HashDb module configuration"); + + // Keep track of everything being uploaded + File localVersionFile = new File(moduleDirPath, SHARED_CONFIG_VERSIONS); + File sharedVersionFile = new File(remoteFolder, SHARED_CONFIG_VERSIONS); + Map newVersions = new HashMap<>(); + Map sharedVersions = readVersionsFromFile(sharedVersionFile); + + // Copy the settings file + copyToRemoteFolder(HASHDB_CONFIG_FILE_NAME, moduleDirPath, remoteFolder, true); + copyToRemoteFolder(HASHDB_CONFIG_FILE_NAME_LEGACY, moduleDirPath, remoteFolder, true); + + // Get the list of databases from the file + List databases = getHashFileNamesFromSettingsFile(); + for (String fullPathToDbFile : databases) { + + // Compare the CRC of the local copy with what is stored in the shared folder + publishTask("Deciding whether to upload " + fullPathToDbFile); + String crc = calculateCRC(fullPathToDbFile); + + // Determine full path to db file in remote folder + String sharedName = convertLocalDbPathToShared(fullPathToDbFile); + File sharedDbBaseFolder = new File(remoteFolder, REMOTE_HASH_FOLDER); + File sharedDb = new File(sharedDbBaseFolder, sharedName); + + if (!(sharedVersions.containsKey(fullPathToDbFile) + && sharedVersions.get(fullPathToDbFile).equals(crc) + && sharedDb.exists())) { + + publishTask("Uploading " + fullPathToDbFile); + File sharedDbPath = sharedDb.getParentFile(); + + if (!sharedDbPath.exists()) { + if (!sharedDbPath.mkdirs()) { + throw new SharedConfigurationException("Error creating shared hash database directory " + sharedDbPath.getAbsolutePath()); + } + } + + File dbFile = new File(fullPathToDbFile); + // copy hash db file to the remote folder + copyFile(sharedDbPath, dbFile); + + // check whether the hash db has an index file (.idx) that should also be copied. + // NOTE: only text hash databases (.txt, .hash, .Hash) can have index file. + // it is possible that the hash db file itself is the index file + String fullPathToIndexFile = ""; + if (fullPathToDbFile.endsWith(".txt") || fullPathToDbFile.endsWith(".hash") || fullPathToDbFile.endsWith(".Hash")) { + // check whether index file for this text database is present + // For example, if text db name is "hash_db.txt" then index file name will be "hash_db.txt-md5.idx" + fullPathToIndexFile = fullPathToDbFile + "-md5.idx"; + + // if index file exists, copy it to the remote location + File dbIndexFile = new File(fullPathToIndexFile); + if (dbIndexFile.exists()) { + // copy index file to the remote folder + copyFile(sharedDbPath, dbIndexFile); + } else { + fullPathToIndexFile = ""; + } + } else if (fullPathToDbFile.endsWith(".idx")) { + // hash db file itself is the index file and it has already been copied to the remote location + fullPathToIndexFile = fullPathToDbFile; + } + + // check whether "index of the index" file exists for this hash DB's index file. + // NOTE: "index of the index" file may only exist + // for text hash database index files (i.e ".idx" extension). The index of the + // index file will always have the same name as the index file, + // distinguished only by the "2" in the extension. "index of the index" file + // is optional and may not be present. + if (fullPathToIndexFile.endsWith(".idx")) { + String fullPathToIndexOfIndexFile = fullPathToIndexFile + "2"; // "index of the index" file has same file name and extension ".idx2" + File dbIndexOfIndexFile = new File(fullPathToIndexOfIndexFile); + if (dbIndexOfIndexFile.exists()) { + // copy index of the index file to the remote folder + copyFile(sharedDbPath, dbIndexOfIndexFile); + } + } + } + + newVersions.put(fullPathToDbFile, crc); + } + + // Write the versions of all uploaded files to a file (make local and shared copies) + writeVersionsToFile(localVersionFile, newVersions); + writeVersionsToFile(sharedVersionFile, newVersions); + } + + /** + * Utility method to copy a file + * + * @param sharedDbPath File object of the folder to copy to + * @param dbFile File object of the file to copy + * + * @throws + * viking.configuration.SharedConfiguration.SharedConfigurationException + */ + private void copyFile(File sharedDbPath, File dbFile) throws SharedConfigurationException { + try { + Path path = Paths.get(sharedDbPath.toString(), dbFile.getName()); + if (path.toFile().exists()) { + path.toFile().delete(); + } + FileUtils.copyFileToDirectory(dbFile, sharedDbPath); + } catch (IOException | SecurityException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", dbFile.getAbsolutePath(), sharedDbPath.getAbsolutePath()), ex); + } + } + + /** + * Upload settings and hash databases to the shared folder. The general + * algorithm is: - Copy the general settings in hashsets.xml - For each hash + * database listed in hashsets.xml: - Compare the recorded CRC in the shared + * directory with the one in the local directory - If different, download + * the database - Update the local list of database CRCs Note that databases + * are downloaded to the exact path they were uploaded from. + * + * @param remoteFolder Shared settings folder + * + * @throws SharedConfigurationException + */ + private void downloadHashDbSettings(File remoteFolder) throws SharedConfigurationException { + publishTask("Downloading HashDb module configuration"); + + // Read in the current local and shared database versions + File localVersionFile = new File(moduleDirPath, SHARED_CONFIG_VERSIONS); + File remoteVersionFile = new File(remoteFolder, SHARED_CONFIG_VERSIONS); + Map localVersions = readVersionsFromFile(localVersionFile); + Map remoteVersions = readVersionsFromFile(remoteVersionFile); + + /* + Iterate through remote list + If local needs it, download + + Download remote settings files to local + Download remote versions file to local + HashDbManager reload + */ + File localDb = new File(""); + File sharedDb = new File(""); + try { + for (String path : remoteVersions.keySet()) { + localDb = new File(path); + if ((!localVersions.containsKey(path)) + || (!localVersions.get(path).equals(remoteVersions.get(path))) + || !localDb.exists()) { + // Need to download a new copy if + // - We have no entry for the database in the local versions file + // - The CRC in the local versions file does not match the one in the shared file + // - Local copy of the database does not exist + + if (localDb.exists()) { + String crc = calculateCRC(path); + if (crc.equals(remoteVersions.get(path))) { + // Can skip the download if the local disk has it + // but it's just not in the versions file. This will + // be populated just before refreshing HashDbManager. + continue; + } + } + + publishTask("Downloading " + path); + String sharedName = convertLocalDbPathToShared(path); + File sharedDbBaseFolder = new File(remoteFolder, REMOTE_HASH_FOLDER); + sharedDb = new File(sharedDbBaseFolder, sharedName); + + if (!localDb.getParentFile().exists()) { + if (!localDb.getParentFile().mkdirs()) { + throw new SharedConfigurationException("Error creating hash database directory " + localDb.getParentFile().getAbsolutePath()); + } + } + + // If a copy of the database is loaded, close it before deleting and copying. + if (localDb.exists()) { + List hashDbs = HashDbManager.getInstance().getAllHashSets(); + HashDbManager.HashDb matchingDb = null; + for (HashDbManager.HashDb db : hashDbs) { + try { + if (localDb.getAbsolutePath().equals(db.getDatabasePath()) || localDb.getAbsolutePath().equals(db.getIndexPath())) { + matchingDb = db; + break; + } + } catch (TskCoreException ex) { + throw new SharedConfigurationException(String.format("Error getting hash database path info for %s", localDb.getParentFile().getAbsolutePath()), ex); + } + } + + if (matchingDb != null) { + try { + HashDbManager.getInstance().removeHashDatabase(matchingDb); + } catch (HashDbManager.HashDbManagerException ex) { + throw new SharedConfigurationException(String.format("Error updating hash database info for %s", localDb.getAbsolutePath()), ex); + } + + } + if (localDb.exists()) { + localDb.delete(); + } + } + FileUtils.copyFile(sharedDb, localDb); + + // check whether the hash db has an index file (.idx) that should also be copied. + // NOTE: only text hash databases (.txt, .hash, .Hash) can have index file. + // it is possible that the hash db file itself is the index file + String fullPathToRemoteDbFile = sharedDb.getPath(); + String fullPathToRemoteIndexFile = ""; + String fullPathToLocalIndexFile = ""; + if (fullPathToRemoteDbFile.endsWith(".txt") || fullPathToRemoteDbFile.toLowerCase().endsWith(".hash")) { + // check whether index file for this text database is present + // For example, if text db name is "hash_db.txt" then index file name will be "hash_db.txt-md5.idx" + fullPathToRemoteIndexFile = fullPathToRemoteDbFile + "-md5.idx"; + + // if index file exists, copy it to the remote location + File remoteDbIndexFile = new File(fullPathToRemoteIndexFile); + if (remoteDbIndexFile.exists()) { + // delete local copy of "index of the index" file if one exists + fullPathToLocalIndexFile = localDb.getPath() + "-md5.idx"; + File localIndexFile = new File(fullPathToLocalIndexFile); + if (localIndexFile.exists()) { + localIndexFile.delete(); + } + // copy index file to the remote folder + FileUtils.copyFile(remoteDbIndexFile, localIndexFile); + } else { + // index file doesn't exist at remote location + fullPathToRemoteIndexFile = ""; + } + } else if (fullPathToRemoteDbFile.endsWith(".idx")) { + // hash db file itself is the index file and it has already been copied to the remote location + fullPathToRemoteIndexFile = fullPathToRemoteDbFile; + fullPathToLocalIndexFile = localDb.getPath(); + } + + // check whether "index of the index" file exists for this hash DB index file. + // NOTE: "index of the index" file may only exist for hash database index files (.idx files). + // For example, hash_db.txt-md5.idx index file will have hash_db.txt-md5.idx2 "index of the index" file. + // "index of the index" file is optional and may not be present. + if (fullPathToRemoteIndexFile.endsWith(".idx")) { + // check if "index of the index" file exists in remote shared config folder + String fullPathToRemoteIndexOfIndexFile = fullPathToRemoteIndexFile + "2"; // "index of the index" file has same file name with extension ".idx2" + File remoteIndexOfIndexFile = new File(fullPathToRemoteIndexOfIndexFile); + if (remoteIndexOfIndexFile.exists()) { + + // delete local copy of "index of the index" file if one exists + String fullPathToLocalIndexOfIndexFile = fullPathToLocalIndexFile + "2"; // "index of the index" file has same file name with extension ".idx2" + File localIndexOfIndexFile = new File(fullPathToLocalIndexOfIndexFile); + if (localIndexOfIndexFile.exists()) { + localIndexOfIndexFile.delete(); + } + // copy index of the index file to the local folder + FileUtils.copyFile(remoteIndexOfIndexFile, localIndexOfIndexFile); + } + } + } + } + } catch (IOException | SecurityException ex) { + throw new SharedConfigurationException(String.format("Failed to copy %s to %s", sharedDb.getAbsolutePath(), localDb.getAbsolutePath()), ex); + } + + // Copy the settings filey + copyToLocalFolder(HASHDB_CONFIG_FILE_NAME, moduleDirPath, remoteFolder, true); + copyToLocalFolder(HASHDB_CONFIG_FILE_NAME_LEGACY, moduleDirPath, remoteFolder, true); + copyToLocalFolder(SHARED_CONFIG_VERSIONS, moduleDirPath, remoteFolder, true); + + // Refresh HashDbManager with the new settings + HashDbManager.getInstance().loadLastSavedConfiguration(); + } + + /** + * Read in the hashsets settings to pull out the names of the databases. + * + * @return List of all hash databases + * + * @throws SharedConfigurationException + */ + private static List getHashFileNamesFromSettingsFile() throws SharedConfigurationException { + List results = new ArrayList<>(); + try { + HashDbManager hashDbManager = HashDbManager.getInstance(); + hashDbManager.loadLastSavedConfiguration(); + for (HashDb hashDb : hashDbManager.getAllHashSets()) { + if (hashDb.hasIndexOnly()) { + results.add(hashDb.getIndexPath()); + } else { + results.add(hashDb.getDatabasePath()); + } + } + } catch (TskCoreException ex) { + throw new SharedConfigurationException("Unable to read hash databases", ex); + } + return results; + } + + /** + * Change the database path into a form that can be used to create + * subfolders in the shared folder. + * + * @param localName Database name from the XML file + * + * @return Path with the initial colon removed + */ + private static String convertLocalDbPathToShared(String localName) { + // Replace the colon + String sharedName = localName.replace(":", "__colon__"); + return sharedName; + } + + /** + * Write the list of database paths and versions to a file. + * + * @param versionFile File to write to + * @param versions Map of database name -> version (current using CRCs as + * versions) + * + * @throws SharedConfigurationException + */ + private static void writeVersionsToFile(File versionFile, Map versions) throws SharedConfigurationException { + try (PrintWriter writer = new PrintWriter(versionFile.getAbsoluteFile(), "UTF-8")) { + for (String filename : versions.keySet()) { + writer.println(versions.get(filename) + " " + filename); + } + } catch (FileNotFoundException | UnsupportedEncodingException ex) { + throw new SharedConfigurationException(String.format("Failed to write version info to %s", versionFile), ex); + } + } + + /** + * Read the map of database paths to versions from a file. + * + * @param versionFile File containing the version information + * + * @return Map of database name -> version + * + * @throws SharedConfigurationException + */ + private static Map readVersionsFromFile(File versionFile) throws SharedConfigurationException { + Map versions = new HashMap<>(); + + // If the file does not exist, return an empty map + if (!versionFile.exists()) { + return versions; + } + + // Read in and store each pair + try (BufferedReader reader = new BufferedReader(new FileReader(versionFile))) { + String currentLine = reader.readLine(); + while (null != currentLine) { + if (!currentLine.isEmpty()) { + int index = currentLine.indexOf(' '); // Find the first space + String crc = currentLine.substring(0, index); + String path = currentLine.substring(index + 1); + versions.put(path, crc); + } + currentLine = reader.readLine(); + } + } catch (FileNotFoundException ex) { + throw new SharedConfigurationException(String.format("Failed to find version file %s", versionFile), ex); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to read version info from %s", versionFile), ex); + } + + return versions; + } + + /** + * Calculate the CRC of a file to use to determine if it has changed. + * + * @param filePath File to get the CRC for + * + * @return String containing the CRC + * + * @throws SharedConfigurationException + */ + private static String calculateCRC(String filePath) throws SharedConfigurationException { + File file = new File(filePath); + try { + FileInputStream fileStream = new FileInputStream(file); + CRC32 crc = new CRC32(); + byte[] buffer = new byte[65536]; + int bytesRead = fileStream.read(buffer); + while (-1 != bytesRead) { + crc.update(buffer, 0, bytesRead); + bytesRead = fileStream.read(buffer); + } + return String.valueOf(crc.getValue()); + } catch (IOException ex) { + throw new SharedConfigurationException(String.format("Failed to calculate CRC for %s", file.getAbsolutePath()), ex); + } + } +}