diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
index 2e618379e4..00881c2ca7 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form
@@ -23,17 +23,14 @@
-
+
-
-
-
-
+
@@ -52,13 +49,14 @@
-
-
-
-
+
+
+
+
+
-
+
@@ -67,7 +65,9 @@
-
+
+
+
@@ -79,7 +79,7 @@
-
+
@@ -102,7 +102,7 @@
-
+
@@ -185,7 +185,7 @@
-
+
@@ -201,24 +201,36 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-
@@ -382,6 +394,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
index 435bdc99b4..a62ae4400c 100755
--- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java
@@ -21,6 +21,14 @@ package org.sleuthkit.autopsy.corecomponents;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.StringJoiner;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
@@ -33,6 +41,8 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.GeneralFilter;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.report.ReportBranding;
/**
@@ -42,12 +52,32 @@ import org.sleuthkit.autopsy.report.ReportBranding;
"AutopsyOptionsPanel.logoPanel.border.title=Logo",
"AutopsyOptionsPanel.viewPanel.border.title=View",
"AutopsyOptionsPanel.invalidImageFile.msg=The selected file was not able to be used as an agency logo.",
- "AutopsyOptionsPanel.invalidImageFile.title=Invalid Image File"})
+ "AutopsyOptionsPanel.invalidImageFile.title=Invalid Image File",
+ "AutopsyOptionsPanel.restartNecessaryWarning.text=A restart is necessary for any changes to max memory to take effect.",
+ "AutopsyOptionsPanel.totalMemoryLabel.text=Total System Memory:",
+ "AutopsyOptionsPanel.maxMemoryLabel.text=Maximum JVM Memory:",
+ "AutopsyOptionsPanel.maxMemoryUnitsLabel.text=GB",
+ "AutopsyOptionsPanel.runtimePanel.border.title=Runtime",
+ "AutopsyOptionsPanel.invalidReasonLabel.not64BitInstall.text=JVM memory settings only enabled for 64 bit version",
+ "AutopsyOptionsPanel.invalidReasonLabel.noValueEntered.text=No value entered",
+ "AutopsyOptionsPanel.invalidReasonLabel.invalidCharacters.text=Invalid characters, value must be a positive integer",
+ "# {0} - minimumMemory",
+ "AutopsyOptionsPanel.invalidReasonLabel.underMinMemory.text=Value must be at least {0}GB",
+ "# {0} - systemMemory",
+ "AutopsyOptionsPanel.invalidReasonLabel.overMaxMemory.text=Value must be less than the total system memory of {0}GB",
+ "AutopsyOptionsPanel.invalidReasonLabel.developerMode.text=Memory settings are not available while running in developer mode"})
+
final class AutopsyOptionsPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final JFileChooser fc;
+ private static final String ETC_FOLDER_NAME = "etc";
+ private static final String CONFIG_FILE_EXTENSION = ".conf";
+ private static final long ONE_BILLION = 1000000000L; //used to roughly convert system memory from bytes to gigabytes
+ private static final long MEGA_IN_GIGA = 1024; //used to convert memory settings saved as megabytes to gigabytes
+ private static final int MIN_MEMORY_IN_GB = 2; //the enforced minimum memory in gigabytes
private static final Logger logger = Logger.getLogger(AutopsyOptionsPanel.class.getName());
+ private String initialMemValue = Long.toString(Runtime.getRuntime().maxMemory() / ONE_BILLION);
AutopsyOptionsPanel() {
initComponents();
@@ -56,6 +86,191 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
fc.setMultiSelectionEnabled(false);
fc.setAcceptAllFileFilterUsed(false);
fc.setFileFilter(new GeneralFilter(GeneralFilter.GRAPHIC_IMAGE_EXTS, GeneralFilter.GRAPHIC_IMG_DECR));
+ if (!PlatformUtil.is64BitJVM() || Version.getBuildType() == Version.Type.DEVELOPMENT) {
+ //32 bit JVM has a max heap size of 1.4 gb to 4 gb depending on OS
+ //So disabling the setting of heap size when the JVM is not 64 bit
+ //Is the safest course of action
+ //And the file won't exist in the install folder when running through netbeans
+ memField.setEnabled(false);
+ }
+ systemMemoryTotal.setText(Long.toString(getSystemMemoryInGB()));
+ }
+
+ /**
+ * Get the total system memory in gigabytes which exists on the machine
+ * which the application is running.
+ *
+ * @return the total system memory
+ */
+ private long getSystemMemoryInGB() {
+ long memorySize = ((com.sun.management.OperatingSystemMXBean) ManagementFactory
+ .getOperatingSystemMXBean()).getTotalPhysicalMemorySize();
+ return memorySize / ONE_BILLION;
+ }
+
+ /**
+ * Gets the currently saved max java heap space in gigabytes.
+ *
+ * @return @throws IOException when unable to get a valid setting
+ */
+ private long getCurrentJvmMaxMemoryInGB() throws IOException {
+ String currentXmx = getCurrentXmxValue();
+ char units = '-';
+ Long value = 0L;
+ if (currentXmx.length() > 1) {
+ units = currentXmx.charAt(currentXmx.length() - 1);
+ value = Long.parseLong(currentXmx.substring(0, currentXmx.length() - 1));
+ } else {
+ throw new IOException("No memory setting found in String: " + currentXmx);
+ }
+ //some older .conf files might have the units as megabytes instead of gigabytes
+ switch (units) {
+ case 'g':
+ case 'G':
+ return value;
+ case 'm':
+ case 'M':
+ return value / MEGA_IN_GIGA;
+ default:
+ throw new IOException("Units were not recognized as parsed: " + units);
+ }
+ }
+
+ /*
+ * The value currently saved in the conf file as the max java heap space
+ * available to this application. Form will be an integer followed by a
+ * character indicating units. Helper method for
+ * getCurrentJvmMaxMemoryInGB()
+ *
+ * @return the saved value for the max java heap space
+ *
+ * @throws IOException if the conf file does not exist in either the user
+ * directory or the install directory
+ */
+ private String getCurrentXmxValue() throws IOException {
+ String[] settings;
+ String currentSetting = "";
+ File userConfFile = getUserFolderConfFile();
+ if (!userConfFile.exists()) {
+ settings = getDefaultsFromFileContents(readConfFile(getInstallFolderConfFile()));
+ } else {
+ settings = getDefaultsFromFileContents(readConfFile(userConfFile));
+ }
+ for (String option : settings) {
+ if (option.startsWith("-J-Xmx")) {
+ currentSetting = option.replace("-J-Xmx", "").trim();
+ }
+ }
+ return currentSetting;
+ }
+
+ /**
+ * Get the conf file from the install directory which stores the default
+ * values for the settings.
+ *
+ * @return the file which has the applications default .conf file
+ *
+ * @throws IOException when the file does not exist.
+ */
+ private static File getInstallFolderConfFile() throws IOException {
+ String confFileName = Version.getName() + CONFIG_FILE_EXTENSION;
+ String installFolder = PlatformUtil.getInstallPath();
+ File installFolderEtc = new File(installFolder, ETC_FOLDER_NAME);
+ File installFolderConfigFile = new File(installFolderEtc, confFileName);
+ if (!installFolderConfigFile.exists()) {
+ throw new IOException("Conf file could not be found" + installFolderConfigFile.toString());
+ }
+ return installFolderConfigFile;
+ }
+
+ /**
+ * Get the conf file from the directory which stores the currently in use
+ * settings. Creates the directory for the file if the directory does not
+ * exist.
+ *
+ * @return the file which has the applications current .conf file
+ */
+ private static File getUserFolderConfFile() {
+ String confFileName = Version.getName() + CONFIG_FILE_EXTENSION;
+ File userFolder = PlatformUtil.getUserDirectory();
+ File userEtcFolder = new File(userFolder, ETC_FOLDER_NAME);
+ if (!userEtcFolder.exists()) {
+ userEtcFolder.mkdir();
+ }
+ return new File(userEtcFolder, confFileName);
+ }
+
+ /**
+ * Take the conf file in the install directory and save a copy of it to the
+ * user directory. The copy will be modified to include the current memory
+ * setting.
+ *
+ * @throws IOException when unable to write a conf file or access the
+ * install folders conf file
+ */
+ private void writeEtcConfFile() throws IOException {
+ StringBuilder content = new StringBuilder();
+ List confFile = readConfFile(getInstallFolderConfFile());
+ for (String line : confFile) {
+ if (line.contains("-J-Xmx")) {
+ String[] splitLine = line.split(" ");
+ StringJoiner modifiedLine = new StringJoiner(" ");
+ for (String piece : splitLine) {
+ if (piece.contains("-J-Xmx")) {
+ piece = "-J-Xmx" + memField.getText() + "g";
+ }
+ modifiedLine.add(piece);
+ }
+ content.append(modifiedLine.toString());
+ } else {
+ content.append(line);
+ }
+ content.append("\n");
+ }
+ Files.write(getUserFolderConfFile().toPath(), content.toString().getBytes());
+ }
+
+ /**
+ * Reads a conf file line by line putting each line into a list of strings
+ * which will be returned.
+ *
+ * @param configFile the .conf file which you wish to read.
+ *
+ * @return a list of strings with a string for each line in the conf file
+ * specified.
+ */
+ private static List readConfFile(File configFile) {
+ List lines = new ArrayList<>();
+ if (null != configFile) {
+ Path filePath = configFile.toPath();
+ Charset charset = Charset.forName("UTF-8");
+ try {
+ lines = Files.readAllLines(filePath, charset);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Error reading config file contents. {}", configFile.getAbsolutePath());
+ }
+ }
+ return lines;
+ }
+
+ /**
+ * Find the string in the list of strings which contains the default options
+ * settings and split it into an array of strings containing one element for
+ * each setting specified.
+ *
+ * @param list a list of string representing lines of a .conf file
+ *
+ * @return an array of strings for each argument on the line which has the
+ * default options, returns an empty array of Strings if default
+ * options is not present.
+ */
+ private static String[] getDefaultsFromFileContents(List list) {
+ Optional defaultSettings = list.stream().filter(line -> line.startsWith("default_options=")).findFirst();
+
+ if (defaultSettings.isPresent()) {
+ return defaultSettings.get().replace("default_options=", "").replaceAll("\"", "").split(" ");
+ }
+ return new String[]{};
}
void load() {
@@ -75,6 +290,16 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
} catch (IOException ex) {
logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex);
}
+ if (memField.isEnabled()) {
+ try {
+ initialMemValue = Long.toString(getCurrentJvmMaxMemoryInGB());
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Can't read current Jvm max memory setting from file", ex);
+ memField.setEnabled(false);
+ }
+ memField.setText(initialMemValue);
+ }
+ isMemFieldValid(); //ensure the error message is up to date
}
private void updateAgencyLogo(String path) throws IOException {
@@ -87,7 +312,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
BufferedImage image = ImageIO.read(file); //create it as an image first to support BMP files
if (image == null) {
throw new IOException("Unable to read file as a BufferedImage for file " + file.toString());
- }
+ }
agencyLogoIcon = new ImageIcon(image.getScaledInstance(64, 64, 4));
agencyLogoPreview.setText("");
}
@@ -109,10 +334,17 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
ModuleSettings.setConfigSetting(ReportBranding.MODULE_NAME, ReportBranding.AGENCY_LOGO_PATH_PROP, agencyLogoPathField.getText());
}
}
+ if (memField.isEnabled()) { //if the field could of been changed we need to try and save it
+ try {
+ writeEtcConfFile();
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Unable to save config file to " + PlatformUtil.getUserDirectory() + "\\" + ETC_FOLDER_NAME, ex);
+ }
+ }
}
boolean valid() {
- return true;
+ return isMemFieldValid();
}
/**
@@ -145,6 +377,15 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
jLabelTimeDisplay = new javax.swing.JLabel();
useLocalTimeRB = new javax.swing.JRadioButton();
useGMTTimeRB = new javax.swing.JRadioButton();
+ runtimePanel = new javax.swing.JPanel();
+ maxMemoryLabel = new javax.swing.JLabel();
+ maxMemoryUnitsLabel = new javax.swing.JLabel();
+ totalMemoryLabel = new javax.swing.JLabel();
+ systemMemoryTotal = new javax.swing.JLabel();
+ restartNecessaryWarning = new javax.swing.JLabel();
+ memField = new javax.swing.JTextField();
+ invalidReasonLabel = new javax.swing.JLabel();
+ maxMemoryUnitsLabel1 = new javax.swing.JLabel();
jScrollPane1.setBorder(null);
@@ -187,7 +428,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addComponent(browseLogosButton)))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(agencyLogoPreview, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(149, Short.MAX_VALUE))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
logoPanelLayout.setVerticalGroup(
logoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -286,20 +527,27 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addGroup(viewPanelLayout.createSequentialGroup()
.addGap(10, 10, 10)
.addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(useGMTTimeRB)
- .addComponent(keepCurrentViewerRB)
- .addComponent(useBestViewerRB)
- .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addComponent(useLocalTimeRB)
- .addComponent(dataSourcesHideSlackCB)
- .addComponent(viewsHideSlackCB)
- .addComponent(dataSourcesHideKnownCB)
- .addComponent(viewsHideKnownCB))))
- .addComponent(jLabelHideSlackFiles)
- .addComponent(jLabelTimeDisplay)
- .addComponent(jLabelHideKnownFiles)
- .addComponent(jLabelSelectFile))
- .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addGroup(viewPanelLayout.createSequentialGroup()
+ .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(useGMTTimeRB)
+ .addComponent(keepCurrentViewerRB)
+ .addComponent(useBestViewerRB)
+ .addComponent(dataSourcesHideKnownCB)
+ .addComponent(viewsHideKnownCB))
+ .addGap(0, 0, Short.MAX_VALUE))
+ .addGroup(viewPanelLayout.createSequentialGroup()
+ .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(dataSourcesHideSlackCB)
+ .addComponent(viewsHideSlackCB)
+ .addComponent(useLocalTimeRB))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
+ .addGroup(viewPanelLayout.createSequentialGroup()
+ .addGroup(viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jLabelHideSlackFiles)
+ .addComponent(jLabelTimeDisplay)
+ .addComponent(jLabelHideKnownFiles)
+ .addComponent(jLabelSelectFile))
+ .addGap(30, 30, 30))))
);
viewPanelLayout.setVerticalGroup(
viewPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -330,15 +578,82 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addComponent(useGMTTimeRB))
);
+ runtimePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.runtimePanel.border.title"))); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(maxMemoryLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxMemoryLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(maxMemoryUnitsLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxMemoryUnitsLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(totalMemoryLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.totalMemoryLabel.text")); // NOI18N
+
+ systemMemoryTotal.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
+
+ restartNecessaryWarning.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/warning16.png"))); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(restartNecessaryWarning, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.restartNecessaryWarning.text")); // NOI18N
+
+ memField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
+ memField.addKeyListener(new java.awt.event.KeyAdapter() {
+ public void keyReleased(java.awt.event.KeyEvent evt) {
+ memFieldKeyReleased(evt);
+ }
+ });
+
+ invalidReasonLabel.setForeground(new java.awt.Color(255, 0, 0));
+
+ org.openide.awt.Mnemonics.setLocalizedText(maxMemoryUnitsLabel1, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.maxMemoryUnitsLabel.text")); // NOI18N
+
+ javax.swing.GroupLayout runtimePanelLayout = new javax.swing.GroupLayout(runtimePanel);
+ runtimePanel.setLayout(runtimePanelLayout);
+ runtimePanelLayout.setHorizontalGroup(
+ runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(runtimePanelLayout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(maxMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(totalMemoryLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(memField, javax.swing.GroupLayout.DEFAULT_SIZE, 37, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 17, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGap(18, 18, 18)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)
+ .addComponent(invalidReasonLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ runtimePanelLayout.setVerticalGroup(
+ runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(runtimePanelLayout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(maxMemoryUnitsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(memField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addComponent(invalidReasonLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(maxMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(maxMemoryUnitsLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addComponent(totalMemoryLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(systemMemoryTotal, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
+ );
+
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
+ .addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
- .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(viewPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
- .addComponent(logoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addComponent(runtimePanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
jPanel1Layout.setVerticalGroup(
@@ -346,7 +661,9 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
.addGroup(jPanel1Layout.createSequentialGroup()
.addGap(0, 0, 0)
.addComponent(viewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, 0)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(runtimePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(logoPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0))
);
@@ -358,14 +675,12 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 672, Short.MAX_VALUE)
+ .addComponent(jScrollPane1)
.addGap(0, 0, 0))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 489, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, Short.MAX_VALUE))
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
);
}// //GEN-END:initComponents
@@ -424,6 +739,52 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
}
}//GEN-LAST:event_browseLogosButtonActionPerformed
+ private void memFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_memFieldKeyReleased
+ String memText = memField.getText();
+ if (memText.equals(initialMemValue)) {
+ //if it is still the initial value don't fire change
+ return;
+ }
+ firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
+ }//GEN-LAST:event_memFieldKeyReleased
+
+ /**
+ * Checks that if the mem field is enabled it has a valid value.
+ *
+ * @return true if the memfield is valid false if it is not
+ */
+ private boolean isMemFieldValid() {
+ String memText = memField.getText();
+ invalidReasonLabel.setText("");
+ if (!PlatformUtil.is64BitJVM()) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_not64BitInstall_text());
+ //the panel should be valid when it is a 32 bit jvm because the memfield will be disabled.
+ return true;
+ }
+ if (Version.getBuildType() == Version.Type.DEVELOPMENT) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_developerMode_text());
+ //the panel should be valid when you are running in developer mode because the memfield will be disabled
+ return true;
+ }
+ if (memText.length() == 0) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_noValueEntered_text());
+ return false;
+ }
+ if (memText.replaceAll("[^\\d]", "").length() != memText.length()) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_invalidCharacters_text());
+ return false;
+ }
+ int parsedInt = Integer.parseInt(memText);
+ if (parsedInt < MIN_MEMORY_IN_GB) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_underMinMemory_text(MIN_MEMORY_IN_GB));
+ return false;
+ }
+ if (parsedInt >= getSystemMemoryInGB()) {
+ invalidReasonLabel.setText(Bundle.AutopsyOptionsPanel_invalidReasonLabel_overMaxMemory_text(getSystemMemoryInGB()));
+ return false;
+ }
+ return true;
+ }
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel agencyLogoImageLabel;
private javax.swing.JTextField agencyLogoPathField;
@@ -433,6 +794,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
private javax.swing.ButtonGroup buttonGroup3;
private javax.swing.JCheckBox dataSourcesHideKnownCB;
private javax.swing.JCheckBox dataSourcesHideSlackCB;
+ private javax.swing.JLabel invalidReasonLabel;
private javax.swing.JLabel jLabelHideKnownFiles;
private javax.swing.JLabel jLabelHideSlackFiles;
private javax.swing.JLabel jLabelSelectFile;
@@ -441,6 +803,14 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JRadioButton keepCurrentViewerRB;
private javax.swing.JPanel logoPanel;
+ private javax.swing.JLabel maxMemoryLabel;
+ private javax.swing.JLabel maxMemoryUnitsLabel;
+ private javax.swing.JLabel maxMemoryUnitsLabel1;
+ private javax.swing.JTextField memField;
+ private javax.swing.JLabel restartNecessaryWarning;
+ private javax.swing.JPanel runtimePanel;
+ private javax.swing.JLabel systemMemoryTotal;
+ private javax.swing.JLabel totalMemoryLabel;
private javax.swing.JRadioButton useBestViewerRB;
private javax.swing.JRadioButton useGMTTimeRB;
private javax.swing.JRadioButton useLocalTimeRB;
@@ -448,4 +818,5 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel {
private javax.swing.JCheckBox viewsHideKnownCB;
private javax.swing.JCheckBox viewsHideSlackCB;
// End of variables declaration//GEN-END:variables
+
}
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
index f23dd57f84..d240310b1c 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle.properties
@@ -20,8 +20,7 @@ IngestMessagePanel.totalUniqueMessagesNameVal.text=-
IngestJob.progress.dataSourceIngest.initialDisplayName=Analyzing {0}
IngestJob.progress.dataSourceIngest.displayName={0} for {1}
IngestJob.progress.fileIngest.displayName=Analyzing files from {0}
-IngestJob.progress.fileIngest.cancelMessage=Waiting for {0} on {1}
-IngestJob.progress.cancelling={0} (Cancelling...)
+IngestJob.progress.cancelling=Cancelling...
IngestJob.cancellationDialog.title=Cancel Ingest
IngestDialog.startButton.title=Start
IngestDialog.closeButton.title=Close
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties
index f7af8964cc..2fc82d0dd9 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties
+++ b/Core/src/org/sleuthkit/autopsy/ingest/Bundle_ja.properties
@@ -2,7 +2,7 @@ CTL_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8
HINT_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8\u30a6\u30a3\u30f3\u30c9\u30a6
IngestDialog.closeButton.title=\u9589\u3058\u308b
IngestDialog.startButton.title=\u958b\u59cb
-IngestJob.progress.cancelling={0}\uff08\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026\uff09
+IngestJob.progress.cancelling=\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026
IngestJob.progress.dataSourceIngest.displayName={1}\u306e{0}
IngestJob.progress.fileIngest.displayName={0}\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u89e3\u6790\u4e2d
IngestManager.moduleErr=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a8\u30e9\u30fc
@@ -90,7 +90,6 @@ IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.jobID=\u30b8\u30e7\u30d
ModuleTableModel.colName.module=\u30e2\u30b8\u30e5\u30fc\u30eb
Menu/Tools/RunIngestModules=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u5b9f\u884c
-IngestJob.progress.fileIngest.cancelMessage={1}\u306e{0}\u3092\u5f85\u3063\u3066\u3044\u307e\u3059
IngestManager.OpenEventChannel.Fail.ErrMsg=\u3053\u306e\u30b1\u30fc\u30b9\u3067\u4f7f\u308f\u308c\u3066\u3044\u308b\u304b\u3082\u3057\u308c\u306a\u3044\u4ed6\u306e\u30ce\u30fc\u30c9\u306b\u89e3\u6790\u30d7\u30ed\u30bb\u30b9\u304c\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
IngestManager.OpenEventChannel.Fail.Title=\u63a5\u7d9a\u5931\u6557
IngestJobSettings.moduleSettingsSave.warning={1}\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e{0}\u30e2\u30b8\u30e5\u30fc\u30eb\u7528\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java
index f9caad7683..b8d5b7e359 100755
--- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java
+++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011-2016 Basis Technology Corp.
+ * Copyright 2014-2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -125,10 +125,9 @@ final class DataSourceIngestJob {
* class.
*/
private volatile boolean currentDataSourceIngestModuleCancelled;
+ private final List cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>();
private volatile boolean cancelled;
private volatile IngestJob.CancellationReason cancellationReason = IngestJob.CancellationReason.NOT_CANCELLED;
- private final Object cancellationStateMonitor = new Object();
- private final List cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>();
/**
* A data source ingest job uses the task scheduler singleton to create and
@@ -989,66 +988,25 @@ final class DataSourceIngestJob {
* @param reason The cancellation reason.
*/
void cancel(IngestJob.CancellationReason reason) {
- if (this.doUI) {
- /**
- * Put a cancellation message on data source level ingest progress
- * bar, if it is still running.
- */
- synchronized (this.dataSourceIngestProgressLock) {
- if (dataSourceIngestProgress != null) {
- final String displayName = NbBundle.getMessage(this.getClass(),
- "IngestJob.progress.dataSourceIngest.initialDisplayName",
- dataSource.getName());
- dataSourceIngestProgress.setDisplayName(
- NbBundle.getMessage(this.getClass(),
- "IngestJob.progress.cancelling",
- displayName));
- }
- }
-
- /**
- * Put a cancellation message on the file level ingest progress bar,
- * if it is still running.
- */
- synchronized (this.fileIngestProgressLock) {
- if (this.fileIngestProgress != null) {
- final String displayName = NbBundle.getMessage(this.getClass(),
- "IngestJob.progress.fileIngest.displayName",
- this.dataSource.getName());
- this.fileIngestProgress.setDisplayName(
- NbBundle.getMessage(this.getClass(), "IngestJob.progress.cancelling",
- displayName));
- if (!this.currentFileIngestModule.isEmpty() && !this.currentFileIngestTask.isEmpty()) {
- this.fileIngestProgress.progress(NbBundle.getMessage(this.getClass(),
- "IngestJob.progress.fileIngest.cancelMessage",
- this.currentFileIngestModule, this.currentFileIngestTask));
- }
-
- }
- }
- }
-
- /*
- * If the work is not already done, show this job as cancelled for the
- * given reason.
- */
- if (Stages.FINALIZATION != stage) {
- synchronized (cancellationStateMonitor) {
- /*
- * These fields are volatile for reading, synchronized on the
- * monitor here for writing.
- */
- this.cancelled = true;
- this.cancellationReason = reason;
- }
- }
-
- /**
- * Tell the task scheduler to cancel all pending tasks, i.e., tasks not
- * not being performed by an ingest thread.
- */
+ this.cancelled = true;
+ this.cancellationReason = reason;
DataSourceIngestJob.taskScheduler.cancelPendingTasksForIngestJob(this);
- this.checkForStageCompleted();
+
+ if (this.doUI) {
+ synchronized (this.dataSourceIngestProgressLock) {
+ if (null != dataSourceIngestProgress) {
+ dataSourceIngestProgress.setDisplayName(NbBundle.getMessage(this.getClass(), "IngestJob.progress.dataSourceIngest.initialDisplayName", dataSource.getName()));
+ dataSourceIngestProgress.progress(NbBundle.getMessage(this.getClass(), "IngestJob.progress.cancelling"));
+ }
+ }
+
+ synchronized (this.fileIngestProgressLock) {
+ if (null != this.fileIngestProgress) {
+ this.fileIngestProgress.setDisplayName(NbBundle.getMessage(this.getClass(), "IngestJob.progress.fileIngest.displayName", this.dataSource.getName()));
+ this.fileIngestProgress.progress(NbBundle.getMessage(this.getClass(), "IngestJob.progress.cancelling"));
+ }
+ }
+ }
}
/**
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java
index 830c93325a..dbbca6394b 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java
@@ -280,7 +280,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
return ProcessResult.OK;
}
List keywordListNames = settings.getNamesOfEnabledKeyWordLists();
- SearchRunner.getInstance().startJob(jobId, dataSourceId, keywordListNames);
+ SearchRunner.getInstance().startJob(context, keywordListNames);
startedSearching = true;
}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
index b998294fe4..da1d5092a7 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
- * Copyright 2011 - 2017 Basis Technology Corp.
+ * Copyright 2014 - 2017 Basis Technology Corp.
* Contact: carrier sleuthkit org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -43,6 +43,7 @@ import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.StopWatch;
+import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestServices;
@@ -79,19 +80,15 @@ public final class SearchRunner {
}
/**
- * Add a new job. Searches will be periodically performed after this is
- * called.
*
- * @param jobId Job ID that this is associated with
- * @param dataSourceId Data source that is being indexed and that
- * searches should be restricted to.
- * @param keywordListNames List of keyword lists that will be searched. List
- * contents will be refreshed each search.
+ * @param jobContext
+ * @param keywordListNames
*/
- public synchronized void startJob(long jobId, long dataSourceId, List keywordListNames) {
+ public synchronized void startJob(IngestJobContext jobContext, List keywordListNames) {
+ long jobId = jobContext.getJobId();
if (jobs.containsKey(jobId) == false) {
logger.log(Level.INFO, "Adding job {0}", jobId); //NON-NLS
- SearchJobInfo jobData = new SearchJobInfo(jobId, dataSourceId, keywordListNames);
+ SearchJobInfo jobData = new SearchJobInfo(jobContext, keywordListNames);
jobs.put(jobId, jobData);
}
@@ -266,6 +263,7 @@ public final class SearchRunner {
*/
private class SearchJobInfo {
+ private final IngestJobContext jobContext;
private final long jobId;
private final long dataSourceId;
// mutable state:
@@ -278,15 +276,20 @@ public final class SearchRunner {
private AtomicLong moduleReferenceCount = new AtomicLong(0);
private final Object finalSearchLock = new Object(); //used for a condition wait
- private SearchJobInfo(long jobId, long dataSourceId, List keywordListNames) {
- this.jobId = jobId;
- this.dataSourceId = dataSourceId;
+ private SearchJobInfo(IngestJobContext jobContext, List keywordListNames) {
+ this.jobContext = jobContext;
+ this.jobId = jobContext.getJobId();
+ this.dataSourceId = jobContext.getDataSource().getId();
this.keywordListNames = new ArrayList<>(keywordListNames);
currentResults = new HashMap<>();
workerRunning = false;
currentSearcher = null;
}
+ private IngestJobContext getJobContext() {
+ return jobContext;
+ }
+
private long getJobId() {
return jobId;
}
@@ -435,7 +438,7 @@ public final class SearchRunner {
int keywordsSearched = 0;
for (Keyword keyword : keywords) {
- if (this.isCancelled()) {
+ if (this.isCancelled() || this.job.getJobContext().fileIngestIsCancelled()) {
logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS
return null;
}
@@ -480,7 +483,6 @@ public final class SearchRunner {
if (!newResults.getKeywords().isEmpty()) {
// Write results to BB
-
//scale progress bar more more granular, per result sub-progress, within per keyword
int totalUnits = newResults.getKeywords().size();
subProgresses[keywordsSearched].start(totalUnits);
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
index 4ccc8d76c5..7183cf7346 100755
--- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/StringsTextExtractor.java
@@ -152,7 +152,7 @@ class StringsTextExtractor extends FileTextExtractor {
private static final Logger logger = Logger.getLogger(EnglishOnlyStream.class.getName());
private static final String NLS = Character.toString((char) 10); //new line
- private static final int READ_BUF_SIZE = 256;
+ private static final int READ_BUF_SIZE = 65536;
private static final int MIN_PRINTABLE_CHARS = 4; //num. of chars needed to qualify as a char string
//args
diff --git a/build-windows-installer.xml b/build-windows-installer.xml
index e3f0401632..b82e28ca24 100755
--- a/build-windows-installer.xml
+++ b/build-windows-installer.xml
@@ -109,7 +109,7 @@
-
+
@@ -144,7 +144,7 @@
-
+
@@ -170,7 +170,7 @@
-
+
diff --git a/build.xml b/build.xml
index bba4207f1b..4eaa7df1c8 100755
--- a/build.xml
+++ b/build.xml
@@ -92,7 +92,7 @@
-
+
diff --git a/docs/doxygen-user/images/runtime_settings.PNG b/docs/doxygen-user/images/runtime_settings.PNG
new file mode 100644
index 0000000000..1d02f96a42
Binary files /dev/null and b/docs/doxygen-user/images/runtime_settings.PNG differ
diff --git a/docs/doxygen-user/installation.dox b/docs/doxygen-user/installation.dox
index c924708201..aa67e55d7a 100755
--- a/docs/doxygen-user/installation.dox
+++ b/docs/doxygen-user/installation.dox
@@ -6,12 +6,16 @@
It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them.
-
\section install Deployment Types
Starting with Autopsy 4.0, there are two ways to deploy Autopsy:
- **Single-User**: Cases can be open by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process.
- **Multi-User**: Cases can be open by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page.
+\section sysreqs System Memory Requirements
+The 64 bit version of Autopsy requires a minimum of 8GB RAM (16 GB recommended).
+When the 64 bit version of Autopsy is installed on Windows it will be limited to a maximum heap size of 4GB leaving the remaining memory for the operating system, the internal Solr text indexing service and other applications. If you wish to change the maximum heap size you can do so after installation by changing the Maximum JVM Memory value in the Runtime section under Tools -> Options -> Application.
+
+\image html runtime_settings.PNG
\section download Download
Download Autopsy from the website:
diff --git a/nbproject/project.properties b/nbproject/project.properties
index ac6c347444..04a6d5800b 100755
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -16,7 +16,7 @@ update_versions=false
#custom JVM options
#Note: can be higher on 64 bit systems, should be in sync with build.xml
# for Japanese version add: -J-Duser.language=ja
-run.args.extra=-J-Xms24m -J-XX:MaxPermSize=128M -J-Xverify:none -J-XX:+UseG1GC -J-XX:+UseStringDeduplication
+run.args.extra=-J-Xms24m -J-Xmx4g -J-XX:MaxPermSize=128M -J-Xverify:none -J-XX:+UseG1GC -J-XX:+UseStringDeduplication
auxiliary.org-netbeans-modules-apisupport-installer.license-type=apache.v2
auxiliary.org-netbeans-modules-apisupport-installer.os-linux=false
auxiliary.org-netbeans-modules-apisupport-installer.os-macosx=false