mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
merge in develop and fixed conflicts
This commit is contained in:
commit
886cc3a696
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A runnable that
|
||||
* - copy the logical image folder to a destination folder
|
||||
* - add alert.txt and users.txt files to report
|
||||
* - add an image data source to the case database.
|
||||
*/
|
||||
public class AddLogicalImageTask extends AddImageTask {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(AddLogicalImageTask.class.getName());
|
||||
private final static String ALERT_TXT = "alert.txt"; //NON-NLS
|
||||
private final static String USERS_TXT = "users.txt"; //NON-NLS
|
||||
private final File src;
|
||||
private final File dest;
|
||||
private final DataSourceProcessorCallback callback;
|
||||
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||
|
||||
public AddLogicalImageTask(String deviceId, String imagePath, int sectorSize,
|
||||
String timeZone, boolean ignoreFatOrphanFiles,
|
||||
String md5, String sha1, String sha256,
|
||||
ImageWriterSettings imageWriterSettings,
|
||||
File src, File dest,
|
||||
DataSourceProcessorProgressMonitor progressMonitor,
|
||||
DataSourceProcessorCallback callback
|
||||
) {
|
||||
super(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles,
|
||||
md5, sha1, sha256, imageWriterSettings, progressMonitor, callback);
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
this.progressMonitor = progressMonitor;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the src directory to dest.
|
||||
* Add alert.txt and users.txt to the case report
|
||||
* Adds the image to the case database.
|
||||
*/
|
||||
@Messages({
|
||||
"# {0} - src", "# {1} - dest", "AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}",
|
||||
"AddLogicalImageTask.doneCopying=Done copying",
|
||||
"# {0} - src", "# {1} - dest", "AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}",
|
||||
"# {0} - file", "AddLogicalImageTask.addingToReport=Adding {0} to report",
|
||||
"# {0} - file", "AddLogicalImageTask.doneAddingToReport=Done adding {0} to report"
|
||||
})
|
||||
@Override
|
||||
public void run() {
|
||||
List<String> errorList = new ArrayList<>();
|
||||
List<Content> emptyDataSources = new ArrayList<>();
|
||||
|
||||
try {
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString()));
|
||||
FileUtils.copyDirectory(src, dest);
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneCopying());
|
||||
} catch (IOException ex) {
|
||||
// Copy directory failed
|
||||
String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString());
|
||||
errorList.add(msg);
|
||||
logger.log(Level.SEVERE, String.format("Failed to copy directory {0} to {1}", src.toString(), dest.toString()));
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the alert.txt and users.txt to the case report
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(ALERT_TXT));
|
||||
String status = addReport(Paths.get(dest.toString(), ALERT_TXT), ALERT_TXT + " " + src.getName());
|
||||
if (status != null) {
|
||||
errorList.add(status);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(ALERT_TXT));
|
||||
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(USERS_TXT));
|
||||
status = addReport(Paths.get(dest.toString(), USERS_TXT), USERS_TXT + " " + src.getName());
|
||||
if (status != null) {
|
||||
errorList.add(status);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT));
|
||||
|
||||
super.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file specified by the reportPath to the case report.
|
||||
*
|
||||
* @param reportPath Path to the report to be added
|
||||
* @param reportName Name associated the report
|
||||
* @returns null if success, or exception message if failure
|
||||
*
|
||||
*/
|
||||
@Messages({
|
||||
"# {0} - file", "# {1} - exception message", "AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}"
|
||||
})
|
||||
private String addReport(Path reportPath, String reportName) {
|
||||
if (!reportPath.toFile().exists()) {
|
||||
return null; // if the reportPath doesn't exist, just ignore it.
|
||||
}
|
||||
try {
|
||||
Case.getCurrentCase().addReport(reportPath.toString(), "LogicalImager", reportName); //NON-NLS
|
||||
return null;
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = Bundle.AddLogicalImageTask_failedToAddReport(reportPath.toString(), ex.getMessage());
|
||||
logger.log(Level.SEVERE, String.format("Failed to add report {0}. Reason= {1}", reportPath.toString(), ex.getMessage()));
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,19 @@
|
||||
AddImageWizardIngestConfigPanel.name.text=Configure Ingest Modules
|
||||
AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode
|
||||
# {0} - file
|
||||
AddLogicalImageTask.addingToReport=Adding {0} to report
|
||||
# {0} - src
|
||||
# {1} - dest
|
||||
AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1}
|
||||
# {0} - file
|
||||
AddLogicalImageTask.doneAddingToReport=Done adding {0} to report
|
||||
AddLogicalImageTask.doneCopying=Done copying
|
||||
# {0} - file
|
||||
# {1} - exception message
|
||||
AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1}
|
||||
# {0} - src
|
||||
# {1} - dest
|
||||
AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}
|
||||
# {0} - exception message
|
||||
Case.closeException.couldNotCloseCase=Error closing case: {0}
|
||||
Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory
|
||||
@ -169,10 +183,19 @@ LogicalEvidenceFilePanel.validatePanel.nonL01Error.text=Only files with the .l01
|
||||
LogicalFilesDspPanel.subTypeComboBox.l01FileOption.text=Logical evidence file (L01)
|
||||
LogicalFilesDspPanel.subTypeComboBox.localFilesOption.text=Local files and folders
|
||||
LogicalImagerDSProcessor.dataSourceType=Autopsy Imager
|
||||
# {0} - directory
|
||||
LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists
|
||||
# {0} - directory
|
||||
LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}
|
||||
# {0} - imageDirPath
|
||||
LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.
|
||||
LogicalImagerPanel.imageTable.columnModel.title0=Hostname
|
||||
LogicalImagerPanel.imageTable.columnModel.title1=Extracted Date
|
||||
LogicalImagerPanel.messageLabel.clickScanOrBrowse=Click SCAN or BROWSE button to find images
|
||||
# {0} - sparseImageDirectory
|
||||
# {1} - image
|
||||
LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}
|
||||
# {0} - invalidFormatDirectory
|
||||
LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS
|
||||
LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images
|
||||
LogicalImagerPanel.messageLabel.noExternalDriveFound=No drive found
|
||||
|
@ -18,7 +18,13 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.swing.JPanel;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
@ -26,6 +32,7 @@ import org.openide.util.lookup.ServiceProviders;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* A Logical Imager data source processor that implements the DataSourceProcessor service
|
||||
@ -38,7 +45,10 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgress
|
||||
)
|
||||
public class LogicalImagerDSProcessor implements DataSourceProcessor {
|
||||
|
||||
private static final String LOGICAL_IMAGER_DIR = "LogicalImager"; //NON-NLS
|
||||
private static final String SPARSE_IMAGE_VHD = "sparse_image.vhd"; //NON-NLS
|
||||
private final LogicalImagerPanel configPanel;
|
||||
private AddLogicalImageTask addLogicalImageTask;
|
||||
|
||||
/*
|
||||
* Constructs a Logical Imager data source processor that implements the
|
||||
@ -114,12 +124,55 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor {
|
||||
* @param callback Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
@Messages({
|
||||
"# {0} - imageDirPath", "LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.",
|
||||
"# {0} - directory", "LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}",
|
||||
"# {0} - directory", "LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists",
|
||||
})
|
||||
@Override
|
||||
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
configPanel.storeSettings();
|
||||
Path dirPath = configPanel.getImageDirPath();
|
||||
System.out.println("Choosen directory " + dirPath.toString());
|
||||
// TODO: process the data source in 5011
|
||||
|
||||
Path imageDirPath = configPanel.getImageDirPath();
|
||||
List<String> errorList = new ArrayList<>();
|
||||
List<Content> emptyDataSources = new ArrayList<>();
|
||||
|
||||
if (!imageDirPath.toFile().exists()) {
|
||||
// This can happen if the USB drive was selected in the panel, but
|
||||
// was ejected before pressing the NEXT button
|
||||
// TODO: Better ways to detect ejected USB drive?
|
||||
String msg = Bundle.LogicalImagerDSProcessor_imageDirPathNotFound(imageDirPath.toString());
|
||||
errorList.add(msg);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the LogicalImager directory under ModuleDirectory
|
||||
String moduleDirectory = Case.getCurrentCase().getModuleDirectory();
|
||||
File logicalImagerDir = Paths.get(moduleDirectory, LOGICAL_IMAGER_DIR).toFile();
|
||||
if (!logicalImagerDir.exists() && !logicalImagerDir.mkdir()) {
|
||||
// create failed
|
||||
String msg = Bundle.LogicalImagerDSProcessor_failToCreateDirectory(logicalImagerDir);
|
||||
errorList.add(msg);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
File dest = Paths.get(logicalImagerDir.toString(), imageDirPath.getFileName().toString()).toFile();
|
||||
if (dest.exists()) {
|
||||
// Destination directory already exists
|
||||
String msg = Bundle.LogicalImagerDSProcessor_directoryAlreadyExists(dest.toString());
|
||||
errorList.add(msg);
|
||||
callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources);
|
||||
return;
|
||||
}
|
||||
File src = imageDirPath.toFile();
|
||||
|
||||
String deviceId = UUID.randomUUID().toString();
|
||||
String timeZone = Calendar.getInstance().getTimeZone().getID();
|
||||
boolean ignoreFatOrphanFiles = false;
|
||||
run(deviceId, Paths.get(src.toString(), SPARSE_IMAGE_VHD).toString(), 0,
|
||||
timeZone, ignoreFatOrphanFiles, null, null, null, src, dest,
|
||||
progressMonitor, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,17 +193,28 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor {
|
||||
* @param chunkSize The maximum size of each chunk of the raw
|
||||
* data source as it is divided up into virtual
|
||||
* unallocated space files.
|
||||
* @param src The source directory of image.
|
||||
* @param dest The destination directory to copy the source.
|
||||
* @param progressMonitor Progress monitor for reporting progress
|
||||
* during processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
*/
|
||||
private void run(String deviceId, String imagePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
|
||||
AddImageTask addImageTask = new AddImageTask(deviceId, imagePath, sectorSize, timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, progressMonitor, callback);
|
||||
new Thread(addImageTask).start();
|
||||
private void run(String deviceId, String imagePath, int sectorSize, String timeZone,
|
||||
boolean ignoreFatOrphanFiles, String md5, String sha1, String sha256,
|
||||
File src, File dest,
|
||||
DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback
|
||||
) {
|
||||
addLogicalImageTask = new AddLogicalImageTask(deviceId, imagePath, sectorSize,
|
||||
timeZone, ignoreFatOrphanFiles, md5, sha1, sha256, null, src, dest,
|
||||
progressMonitor, callback);
|
||||
new Thread(addLogicalImageTask).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (addLogicalImageTask != null) {
|
||||
addLogicalImageTask.cancelTask();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Form version="1.6" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<Properties>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[0, 65]"/>
|
||||
@ -213,6 +213,9 @@
|
||||
<Property name="columnModel" type="javax.swing.table.TableColumnModel" editor="org.netbeans.modules.form.editors2.TableColumnModelEditor">
|
||||
<TableColumnModel selectionModel="1"/>
|
||||
</Property>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
<Property name="showHorizontalLines" type="boolean" value="false"/>
|
||||
<Property name="showVerticalLines" type="boolean" value="false"/>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
|
@ -42,15 +42,14 @@ import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
|
||||
|
||||
/**
|
||||
* Panel for adding an logical image file from drive letters. Allows the user
|
||||
* to select a file.
|
||||
* Panel for adding an logical image file from drive letters. Allows the user to
|
||||
* select a file.
|
||||
*/
|
||||
@Messages({
|
||||
"LogicalImagerPanel.messageLabel.selectedImage=Selected folder",
|
||||
"LogicalImagerPanel.messageLabel.noImageSelected=No image selected",
|
||||
"LogicalImagerPanel.messageLabel.driveHasNoImages=Drive has no images",
|
||||
"LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive",
|
||||
})
|
||||
"LogicalImagerPanel.selectAcquisitionFromDriveLabel.text=Select acquisition from Drive",})
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
|
||||
@ -70,8 +69,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
/**
|
||||
* Creates new form LogicalImagerPanel
|
||||
*
|
||||
* @param context A string context name used to read/store last
|
||||
* used settings.
|
||||
* @param context A string context name used to read/store last used
|
||||
* settings.
|
||||
*/
|
||||
private LogicalImagerPanel(String context) {
|
||||
this.contextName = context;
|
||||
@ -82,8 +81,8 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
/**
|
||||
* Creates and returns an instance of a LogicalImagerPanel.
|
||||
*
|
||||
* @param context A string context name used to read/store last
|
||||
* used settings.
|
||||
* @param context A string context name used to read/store last used
|
||||
* settings.
|
||||
*
|
||||
* @return instance of the LogicalImagerPanel
|
||||
*/
|
||||
@ -173,6 +172,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
}
|
||||
));
|
||||
imageTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
|
||||
imageTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
imageTable.setShowHorizontalLines(false);
|
||||
imageTable.setShowVerticalLines(false);
|
||||
imageTable.getTableHeader().setReorderingAllowed(false);
|
||||
@ -264,7 +264,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
return bytes + " B"; //NON-NLS
|
||||
}
|
||||
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
||||
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); //NON-NLS
|
||||
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); //NON-NLS
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); //NON-NLS
|
||||
}
|
||||
|
||||
@ -310,7 +310,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
}//GEN-LAST:event_scanButtonActionPerformed
|
||||
|
||||
@Messages({
|
||||
"# {0} - sparseImageDirectory",
|
||||
"# {1} - image",
|
||||
"LogicalImagerPanel.messageLabel.directoryDoesNotContainSparseImage=Directory {0} does not contain {1}",
|
||||
"# {0} - invalidFormatDirectory",
|
||||
"LogicalImagerPanel.messageLabel.directoryFormatInvalid=Directory {0} does not match format Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS"
|
||||
})
|
||||
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
|
||||
@ -325,7 +328,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
if (m.find()) {
|
||||
Path vhdPath = Paths.get(path, SPARSE_IMAGE_VHD);
|
||||
if (!vhdPath.toFile().exists()) {
|
||||
setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path,SPARSE_IMAGE_VHD));
|
||||
setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path, SPARSE_IMAGE_VHD));
|
||||
firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false);
|
||||
return;
|
||||
}
|
||||
@ -374,7 +377,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
// and has a sparse_image.vhd file in it
|
||||
for (File file : fList) {
|
||||
if (file.isDirectory()
|
||||
&& Paths.get(driveLetter, file.getName(), SPARSE_IMAGE_VHD).toFile().exists()) {
|
||||
&& Paths.get(driveLetter, file.getName(), SPARSE_IMAGE_VHD).toFile().exists()) {
|
||||
String dir = file.getName();
|
||||
Matcher m = regex.matcher(dir);
|
||||
if (m.find()) {
|
||||
@ -485,6 +488,10 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
return choosenImageDirPath;
|
||||
}
|
||||
|
||||
public void setMessageLabel(String message) {
|
||||
messageLabel.setText(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
}
|
||||
@ -501,6 +508,7 @@ public class LogicalImagerPanel extends JPanel implements DocumentListener {
|
||||
}
|
||||
|
||||
private class ImageTableModel extends AbstractTableModel {
|
||||
|
||||
private final List<String> hostnames = new ArrayList<>();
|
||||
private final List<String> extractDates = new ArrayList<>();
|
||||
private final List<String> imageDirPaths = new ArrayList<>();
|
||||
|
@ -22,7 +22,11 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
@ -51,6 +55,7 @@ import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter.AccountTypeFilter;
|
||||
import org.sleuthkit.datamodel.CommunicationsFilter.DateRangeFilter;
|
||||
@ -157,6 +162,29 @@ final public class FiltersPanel extends JPanel {
|
||||
|
||||
applyFiltersButton.addActionListener(e -> applyFilters());
|
||||
refreshButton.addActionListener(e -> applyFilters());
|
||||
|
||||
try {
|
||||
String queryString = "max(date_time) as max, min(date_time) as min from account_relationships"; // NON-NLS
|
||||
Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(queryString, new FilterPanelQueryCallback() {
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
try {
|
||||
if (rs.next()) {
|
||||
int startDate = rs.getInt("min"); // NON-NLS
|
||||
int endData = rs.getInt("max"); // NON-NLS
|
||||
|
||||
startDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(startDate), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
endDatePicker.setDate(LocalDateTime.ofInstant(Instant.ofEpochSecond(endData), Utils.getUserPreferredZoneId()).toLocalDate());
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.log(Level.WARNING, "Unable to set filter date pickers due to SQL exception", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to set filter date pickers due to exception", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -958,4 +986,17 @@ final public class FiltersPanel extends JPanel {
|
||||
checkbox.addItemListener(l);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class that implements CaseDbAccessQueryCallback. Can be used
|
||||
* as an anonymous innerclass with the CaseDbAccessManager select function.
|
||||
*/
|
||||
class FilterPanelQueryCallback implements CaseDbAccessQueryCallback {
|
||||
|
||||
@Override
|
||||
public void process(ResultSet rs) {
|
||||
// Subclasses can implement their own process function.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -16,11 +16,11 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="htmlScrollPane" pref="300" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<EmptySpace min="0" pref="95" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="htmlJPanel" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
@ -28,29 +28,12 @@
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="htmlScrollPane" pref="71" max="32767" attributes="0"/>
|
||||
<Component id="htmlJPanel" pref="33" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="htmlScrollPane">
|
||||
<Properties>
|
||||
<Property name="verticalScrollBarPolicy" type="int" value="22"/>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextPane" name="htmlbodyTextPane">
|
||||
<Properties>
|
||||
<Property name="editable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JToggleButton" name="showImagesToggleButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
@ -61,5 +44,9 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="showImagesToggleButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="htmlJPanel">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -18,9 +18,19 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.contentviewers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.w3c.dom.events.EventTarget;
|
||||
|
||||
/**
|
||||
* A file content viewer for HTML files.
|
||||
@ -29,7 +39,9 @@ import org.openide.util.NbBundle.Messages;
|
||||
final class HtmlPanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TEXT_TYPE = "text/plain";
|
||||
private final JFXPanel jfxPanel = new JFXPanel();
|
||||
private WebView webView;
|
||||
private String htmlText;
|
||||
|
||||
/**
|
||||
@ -37,8 +49,26 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
*/
|
||||
HtmlPanel() {
|
||||
initComponents();
|
||||
|
||||
Utilities.configureTextPaneAsHtml(htmlbodyTextPane);
|
||||
Platform.runLater(() -> {
|
||||
webView = new WebView();
|
||||
//disable the context menu so they can't open linked pages by right clicking
|
||||
webView.setContextMenuEnabled(false);
|
||||
//disable java script
|
||||
webView.getEngine().setJavaScriptEnabled(false);
|
||||
//disable clicking on links
|
||||
webView.getEngine().getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
disableHyperLinks();
|
||||
}
|
||||
}
|
||||
});
|
||||
Scene scene = new Scene(webView);
|
||||
jfxPanel.setScene(scene);
|
||||
jfxPanel.setPreferredSize(htmlJPanel.getPreferredSize());
|
||||
htmlJPanel.add(jfxPanel);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,21 +85,12 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
* Clear the HTML in the text pane and disable the show/hide button.
|
||||
*/
|
||||
void reset() {
|
||||
htmlbodyTextPane.setText("");
|
||||
Platform.runLater(() -> {
|
||||
webView.getEngine().loadContent("", TEXT_TYPE);
|
||||
});
|
||||
showImagesToggleButton.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarantee the HTML text has 'html' and 'body' tags.
|
||||
*
|
||||
* @param htmlText The HTML text
|
||||
*
|
||||
* @return The HTML text with the 'html' and 'body' tags applied.
|
||||
*/
|
||||
private String wrapInHtmlBody(String htmlText) {
|
||||
return "<html><body>" + htmlText + "</body></html>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans out input HTML string
|
||||
*
|
||||
@ -78,12 +99,11 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
* @return The cleansed HTML String
|
||||
*/
|
||||
private String cleanseHTML(String htmlInString) {
|
||||
|
||||
Document doc = Jsoup.parse(htmlInString);
|
||||
|
||||
// Update all 'img' tags.
|
||||
doc.select("img[src]").forEach(img -> img.attr("src", ""));
|
||||
|
||||
org.jsoup.nodes.Document doc = Jsoup.parse(htmlInString);
|
||||
// remove all 'img' tags.
|
||||
doc.select("img").stream().forEach(Node::remove);
|
||||
// remove all 'span' tags, these are often images which are ads
|
||||
doc.select("span").stream().forEach(Node::remove);
|
||||
return doc.html();
|
||||
}
|
||||
|
||||
@ -93,22 +113,26 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
@Messages({
|
||||
"HtmlPanel_showImagesToggleButton_show=Show Images",
|
||||
"HtmlPanel_showImagesToggleButton_hide=Hide Images",
|
||||
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",
|
||||
})
|
||||
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",})
|
||||
private void refresh() {
|
||||
if (false == htmlText.isEmpty()) {
|
||||
try {
|
||||
if (showImagesToggleButton.isSelected()) {
|
||||
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_hide());
|
||||
this.htmlbodyTextPane.setText(wrapInHtmlBody(htmlText));
|
||||
Platform.runLater(() -> {
|
||||
webView.getEngine().loadContent(htmlText);
|
||||
});
|
||||
} else {
|
||||
showImagesToggleButton.setText(Bundle.HtmlPanel_showImagesToggleButton_show());
|
||||
this.htmlbodyTextPane.setText(wrapInHtmlBody(cleanseHTML(htmlText)));
|
||||
Platform.runLater(() -> {
|
||||
webView.getEngine().loadContent(cleanseHTML(htmlText));
|
||||
});
|
||||
}
|
||||
showImagesToggleButton.setEnabled(true);
|
||||
htmlbodyTextPane.setCaretPosition(0);
|
||||
} catch(Exception ex) {
|
||||
this.htmlbodyTextPane.setText(wrapInHtmlBody(Bundle.Html_text_display_error()));
|
||||
} catch (Exception ignored) {
|
||||
Platform.runLater(() -> {
|
||||
webView.getEngine().loadContent(Bundle.Html_text_display_error(), TEXT_TYPE);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,14 +146,8 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
htmlScrollPane = new javax.swing.JScrollPane();
|
||||
htmlbodyTextPane = new javax.swing.JTextPane();
|
||||
showImagesToggleButton = new javax.swing.JToggleButton();
|
||||
|
||||
htmlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
|
||||
htmlbodyTextPane.setEditable(false);
|
||||
htmlScrollPane.setViewportView(htmlbodyTextPane);
|
||||
htmlJPanel = new javax.swing.JPanel();
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(showImagesToggleButton, org.openide.util.NbBundle.getMessage(HtmlPanel.class, "HtmlPanel.showImagesToggleButton.text")); // NOI18N
|
||||
showImagesToggleButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
@ -138,21 +156,23 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
htmlJPanel.setLayout(new java.awt.BorderLayout());
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(showImagesToggleButton)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGap(0, 95, Short.MAX_VALUE))
|
||||
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(showImagesToggleButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(htmlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 71, Short.MAX_VALUE))
|
||||
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 33, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@ -160,10 +180,27 @@ final class HtmlPanel extends javax.swing.JPanel {
|
||||
refresh();
|
||||
}//GEN-LAST:event_showImagesToggleButtonActionPerformed
|
||||
|
||||
/**
|
||||
* Disable the click events on hyper links so that new pages can not be
|
||||
* opened.
|
||||
*/
|
||||
private void disableHyperLinks() {
|
||||
Platform.runLater(() -> {
|
||||
Document document = webView.getEngine().getDocument();
|
||||
if (document != null) {
|
||||
NodeList nodeList = document.getElementsByTagName("a");
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
((EventTarget) nodeList.item(i)).addEventListener("click", (evt) -> {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JScrollPane htmlScrollPane;
|
||||
private javax.swing.JTextPane htmlbodyTextPane;
|
||||
private javax.swing.JPanel htmlJPanel;
|
||||
private javax.swing.JToggleButton showImagesToggleButton;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ public final class UserPreferences {
|
||||
public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames";
|
||||
public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath";
|
||||
public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize";
|
||||
public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize";
|
||||
|
||||
// Prevent instantiation.
|
||||
private UserPreferences() {
|
||||
@ -500,6 +501,24 @@ public final class UserPreferences {
|
||||
preferences.putInt(SOLR_MAX_JVM_SIZE, maxSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of results to display in a result table.
|
||||
*
|
||||
* @return Saved value or default (0) which indicates no max.
|
||||
*/
|
||||
public static int getResultsTablePageSize() {
|
||||
return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of results to display in a result table.
|
||||
*
|
||||
* @param pageSize
|
||||
*/
|
||||
public static void setResultsTablePageSize(int pageSize) {
|
||||
preferences.putInt(RESULTS_TABLE_PAGE_SIZE, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HdX path.
|
||||
*
|
||||
|
@ -207,3 +207,13 @@ ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extensi
|
||||
AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
|
||||
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
|
||||
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance.
|
||||
DataResultViewerTable.gotoPageTextField.text=
|
||||
DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName=
|
||||
DataResultViewerTable.gotoPageLabel.text=Go to Page:
|
||||
DataResultViewerTable.pageNextButton.text=
|
||||
DataResultViewerTable.pagePrevButton.text=
|
||||
DataResultViewerTable.pagesLabel.text=Pages:
|
||||
DataResultViewerTable.pageNumLabel.text=
|
||||
DataResultViewerTable.pageLabel.text=Page:
|
||||
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
|
||||
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nAll results are shown in the results table if this value is 0 (default).\n<br>You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n</html>
|
||||
|
@ -33,6 +33,12 @@ DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on assoc
|
||||
DataResultViewerTable.countRender.name=O
|
||||
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
|
||||
DataResultViewerTable.firstColLbl=Name
|
||||
DataResultViewerTable.goToPageTextField.err=Invalid page number
|
||||
# {0} - totalPages
|
||||
DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}
|
||||
# {0} - currentPage
|
||||
# {1} - totalPages
|
||||
DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}
|
||||
DataResultViewerTable.scoreRender.name=S
|
||||
DataResultViewerTable.scoreRender.toolTip=S(core) indicates whether the item is interesting or notable
|
||||
DataResultViewerTable.title=Table
|
||||
@ -254,3 +260,13 @@ ExternalViewerGlobalSettingsPanel.jTable1.columnModel.title0_1=Mime type/Extensi
|
||||
AutopsyOptionsPanel.maxSolrMemoryLabel.text=Maximum Solr JVM Memory:
|
||||
AutopsyOptionsPanel.maxMemoryUnitsLabel2.text=MB
|
||||
AutopsyOptionsPanel.solrJVMHeapWarning.text=NOTE: Setting this too large may impact overall performance.
|
||||
DataResultViewerTable.gotoPageTextField.text=
|
||||
DataResultViewerTable.gotoPageLabel.AccessibleContext.accessibleName=
|
||||
DataResultViewerTable.gotoPageLabel.text=Go to Page:
|
||||
DataResultViewerTable.pageNextButton.text=
|
||||
DataResultViewerTable.pagePrevButton.text=
|
||||
DataResultViewerTable.pagesLabel.text=Pages:
|
||||
DataResultViewerTable.pageNumLabel.text=
|
||||
DataResultViewerTable.pageLabel.text=Page:
|
||||
ViewPreferencesPanel.maxResultsLabel.text=Maximum number of Results to show in table:
|
||||
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nAll results are shown in the results table if this value is 0 (default).\n<br>You may want to consider setting this value if you have large numbers of results that are taking a long time to display.\n</html>
|
||||
|
@ -16,16 +16,127 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="outlineView" alignment="0" pref="691" max="32767" attributes="0"/>
|
||||
<Component id="outlineView" alignment="0" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace pref="608" max="32767" attributes="0"/>
|
||||
<Component id="pageLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="pageNumLabel" min="-2" pref="53" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="pagesLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="pagePrevButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="pageNextButton" linkSize="1" min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="gotoPageLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="gotoPageTextField" min="-2" pref="33" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="outlineView" alignment="0" pref="366" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="2" attributes="0">
|
||||
<Component id="pageLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="pageNumLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="pagesLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="pagePrevButton" linkSize="2" alignment="2" min="-2" pref="14" max="-2" attributes="0"/>
|
||||
<Component id="pageNextButton" linkSize="2" alignment="2" min="-2" pref="15" max="-2" attributes="0"/>
|
||||
<Component id="gotoPageLabel" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="gotoPageTextField" alignment="2" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="outlineView" pref="324" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="pageLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="pageNumLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNumLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="pagesLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="pagePrevButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pagePrevButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"/>
|
||||
</Property>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
<Property name="horizontalTextPosition" type="int" value="0"/>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[2, 0, 2, 0]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[55, 23]"/>
|
||||
</Property>
|
||||
<Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"/>
|
||||
</Property>
|
||||
<Property name="verticalTextPosition" type="int" value="3"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pagePrevButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="pageNextButton">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.pageNextButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="disabledIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"/>
|
||||
</Property>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
<Property name="horizontalTextPosition" type="int" value="0"/>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[2, 0, 2, 0]"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[27, 23]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[27, 23]"/>
|
||||
</Property>
|
||||
<Property name="rolloverIcon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"/>
|
||||
</Property>
|
||||
<Property name="verticalTextPosition" type="int" value="3"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pageNextButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="org.openide.explorer.view.OutlineView" name="outlineView">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);"/>
|
||||
@ -33,5 +144,25 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="gotoPageLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AccessibilityProperties>
|
||||
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
|
||||
</AccessibilityProperties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="gotoPageTextField">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataResultViewerTable.gotoPageTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gotoPageTextFieldActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012-2018 Basis Technology Corp.
|
||||
* Copyright 2012-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.corecomponents;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.FontMetrics;
|
||||
@ -26,6 +27,7 @@ import java.awt.dnd.DnDConstants;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.FeatureDescriptor;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
@ -35,9 +37,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.PreferenceChangeEvent;
|
||||
import java.util.prefs.Preferences;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import static javax.swing.SwingConstants.CENTER;
|
||||
@ -61,15 +66,24 @@ import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.nodes.Node.Property;
|
||||
import org.openide.nodes.NodeEvent;
|
||||
import org.openide.nodes.NodeListener;
|
||||
import org.openide.nodes.NodeMemberEvent;
|
||||
import org.openide.nodes.NodeReorderEvent;
|
||||
import org.openide.util.ImageUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbPreferences;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageChangeEvent;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageCountChangeEvent;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.PageSizeChangeEvent;
|
||||
|
||||
/**
|
||||
* A tabular result viewer that displays the children of the given root node
|
||||
@ -104,6 +118,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
private final IconRendererTableListener iconRendererListener;
|
||||
private Node rootNode;
|
||||
|
||||
/**
|
||||
* Multiple nodes may have been visited in the context of this
|
||||
* DataResultViewerTable. We keep track of the page state for these nodes in
|
||||
* the following map.
|
||||
*/
|
||||
private final Map<String, PagingSupport> nodeNameToPagingSupportMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* The paging support instance for the current node.
|
||||
*/
|
||||
private PagingSupport pagingSupport = null;
|
||||
|
||||
/**
|
||||
* Constructs a tabular result viewer that displays the children of the
|
||||
* given root node using an OutlineView. The viewer should have an ancestor
|
||||
@ -149,6 +175,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
initComponents();
|
||||
|
||||
initializePagingSupport();
|
||||
|
||||
/*
|
||||
* Configure the child OutlineView (explorer view) component.
|
||||
*/
|
||||
@ -177,6 +205,32 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
outline.getTableHeader().addMouseListener(outlineViewListener);
|
||||
}
|
||||
|
||||
private void initializePagingSupport() {
|
||||
if (pagingSupport == null) {
|
||||
pagingSupport = new PagingSupport("");
|
||||
}
|
||||
|
||||
// Start out with paging controls invisible
|
||||
pagingSupport.togglePageControls(false);
|
||||
|
||||
/**
|
||||
* Set up a change listener so we know when the user changes the page
|
||||
* size
|
||||
*/
|
||||
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
|
||||
if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
/**
|
||||
* If multiple nodes have been viewed we have to notify all of
|
||||
* them about the change in page size.
|
||||
*/
|
||||
nodeNameToPagingSupportMap.values().forEach((ps) -> {
|
||||
ps.postPageSizeChangeEvent();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of a tabular result viewer that displays the
|
||||
* children of a given root node using an OutlineView. This method exists to
|
||||
@ -252,6 +306,56 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) {
|
||||
this.rootNode = rootNode;
|
||||
|
||||
/**
|
||||
* Check to see if we have previously created a paging support
|
||||
* class for this node.
|
||||
*/
|
||||
String nodeName = rootNode.getName();
|
||||
pagingSupport = nodeNameToPagingSupportMap.get(nodeName);
|
||||
if (pagingSupport == null) {
|
||||
pagingSupport = new PagingSupport(nodeName);
|
||||
nodeNameToPagingSupportMap.put(nodeName, pagingSupport);
|
||||
}
|
||||
pagingSupport.updateControls();
|
||||
|
||||
rootNode.addNodeListener(new NodeListener() {
|
||||
@Override
|
||||
public void childrenAdded(NodeMemberEvent nme) {
|
||||
/**
|
||||
* This is the only somewhat reliable way I could find
|
||||
* to reset the cursor after a page change. When you
|
||||
* change page the old children nodes will be removed
|
||||
* and new ones added.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setCursor(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childrenRemoved(NodeMemberEvent nme) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setCursor(null);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childrenReordered(NodeReorderEvent nre) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nodeDestroyed(NodeEvent ne) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
// No-op
|
||||
}
|
||||
});
|
||||
|
||||
this.getExplorerManager().setRootContext(this.rootNode);
|
||||
setupTable();
|
||||
} else {
|
||||
@ -665,6 +769,152 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintains the current page state for a node and provides support for
|
||||
* paging through results. Uses an EventBus to communicate with child
|
||||
* factory implementations.
|
||||
*/
|
||||
private class PagingSupport {
|
||||
|
||||
private int currentPage;
|
||||
private int totalPages;
|
||||
private final String nodeName;
|
||||
|
||||
PagingSupport(String nodeName) {
|
||||
currentPage = 1;
|
||||
totalPages = 0;
|
||||
this.nodeName = nodeName;
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
if (!nodeName.isEmpty()) {
|
||||
BaseChildFactory.register(nodeName, this);
|
||||
}
|
||||
updateControls();
|
||||
}
|
||||
|
||||
void nextPage() {
|
||||
currentPage++;
|
||||
postPageChangeEvent();
|
||||
}
|
||||
|
||||
void previousPage() {
|
||||
currentPage--;
|
||||
postPageChangeEvent();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - totalPages",
|
||||
"DataResultViewerTable.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}",
|
||||
"DataResultViewerTable.goToPageTextField.err=Invalid page number"})
|
||||
void gotoPage() {
|
||||
try {
|
||||
currentPage = Integer.decode(gotoPageTextField.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
//ignore input
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPage > totalPages || currentPage < 1) {
|
||||
currentPage = 1;
|
||||
JOptionPane.showMessageDialog(DataResultViewerTable.this,
|
||||
Bundle.DataResultViewerTable_goToPageTextField_msgDlg(totalPages),
|
||||
Bundle.DataResultViewerTable_goToPageTextField_err(),
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
postPageChangeEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify subscribers (i.e. child factories) that a page change has
|
||||
* occurred.
|
||||
*/
|
||||
void postPageChangeEvent() {
|
||||
try {
|
||||
BaseChildFactory.post(nodeName, new PageChangeEvent(currentPage));
|
||||
} catch (BaseChildFactory.NoSuchEventBusException ex) {
|
||||
LOGGER.log(Level.WARNING, "Failed to post page change event.", ex); //NON-NLS
|
||||
}
|
||||
DataResultViewerTable.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
updateControls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify subscribers (i.e. child factories) that a page size change has
|
||||
* occurred.
|
||||
*/
|
||||
void postPageSizeChangeEvent() {
|
||||
// Reset page variables when page size changes
|
||||
currentPage = 1;
|
||||
totalPages = 0;
|
||||
|
||||
if (this == pagingSupport) {
|
||||
updateControls();
|
||||
}
|
||||
try {
|
||||
BaseChildFactory.post(nodeName, new PageSizeChangeEvent(UserPreferences.getResultsTablePageSize()));
|
||||
} catch (BaseChildFactory.NoSuchEventBusException ex) {
|
||||
LOGGER.log(Level.WARNING, "Failed to post page size change event.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to notification that the number of pages has changed.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@Subscribe
|
||||
public void subscribeToPageCountChange(PageCountChangeEvent event) {
|
||||
if (event != null) {
|
||||
totalPages = event.getPageCount();
|
||||
if (totalPages > 1) {
|
||||
// Make paging controls visible if there is more than one page.
|
||||
togglePageControls(true);
|
||||
}
|
||||
|
||||
updateControls();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make paging controls visible or invisible based on flag.
|
||||
*
|
||||
* @param onOff
|
||||
*/
|
||||
private void togglePageControls(boolean onOff) {
|
||||
pageLabel.setVisible(onOff);
|
||||
pagesLabel.setVisible(onOff);
|
||||
pagePrevButton.setVisible(onOff);
|
||||
pageNextButton.setVisible(onOff);
|
||||
pageNumLabel.setVisible(onOff);
|
||||
gotoPageLabel.setVisible(onOff);
|
||||
gotoPageTextField.setVisible(onOff);
|
||||
gotoPageTextField.setVisible(onOff);
|
||||
validate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - currentPage", "# {1} - totalPages",
|
||||
"DataResultViewerTable.pageNumbers.curOfTotal={0} of {1}"})
|
||||
private void updateControls() {
|
||||
if (totalPages == 0) {
|
||||
pagePrevButton.setEnabled(false);
|
||||
pageNextButton.setEnabled(false);
|
||||
pageNumLabel.setText("");
|
||||
gotoPageTextField.setText("");
|
||||
gotoPageTextField.setEnabled(false);
|
||||
} else {
|
||||
pageNumLabel.setText(Bundle.DataResultViewerTable_pageNumbers_curOfTotal(Integer.toString(currentPage), Integer.toString(totalPages)));
|
||||
|
||||
pageNextButton.setEnabled(currentPage != totalPages);
|
||||
pagePrevButton.setEnabled(currentPage != 1);
|
||||
gotoPageTextField.setEnabled(totalPages > 1);
|
||||
gotoPageTextField.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener which sets the custom icon renderer on columns which contain
|
||||
* icons instead of text when a column is added.
|
||||
@ -1033,21 +1283,129 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
pageLabel = new javax.swing.JLabel();
|
||||
pageNumLabel = new javax.swing.JLabel();
|
||||
pagesLabel = new javax.swing.JLabel();
|
||||
pagePrevButton = new javax.swing.JButton();
|
||||
pageNextButton = new javax.swing.JButton();
|
||||
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
|
||||
gotoPageLabel = new javax.swing.JLabel();
|
||||
gotoPageTextField = new javax.swing.JTextField();
|
||||
|
||||
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
|
||||
|
||||
pageNumLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNumLabel.text")); // NOI18N
|
||||
|
||||
pagesLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagesLabel.text")); // NOI18N
|
||||
|
||||
pagePrevButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
|
||||
pagePrevButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pagePrevButton.text")); // NOI18N
|
||||
pagePrevButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
|
||||
pagePrevButton.setFocusable(false);
|
||||
pagePrevButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
|
||||
pagePrevButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
|
||||
pagePrevButton.setPreferredSize(new java.awt.Dimension(55, 23));
|
||||
pagePrevButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
|
||||
pagePrevButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
|
||||
pagePrevButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
pagePrevButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
pageNextButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
|
||||
pageNextButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageNextButton.text")); // NOI18N
|
||||
pageNextButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
|
||||
pageNextButton.setFocusable(false);
|
||||
pageNextButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
|
||||
pageNextButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
|
||||
pageNextButton.setMaximumSize(new java.awt.Dimension(27, 23));
|
||||
pageNextButton.setMinimumSize(new java.awt.Dimension(27, 23));
|
||||
pageNextButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
|
||||
pageNextButton.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
|
||||
pageNextButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
pageNextButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
gotoPageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageLabel.text")); // NOI18N
|
||||
|
||||
gotoPageTextField.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.gotoPageTextField.text")); // NOI18N
|
||||
gotoPageTextField.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
gotoPageTextFieldActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE)
|
||||
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap(608, Short.MAX_VALUE)
|
||||
.addComponent(pageLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(pagesLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(gotoPageLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
|
||||
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
|
||||
.addComponent(pageLabel)
|
||||
.addComponent(pageNumLabel)
|
||||
.addComponent(pagesLabel)
|
||||
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(gotoPageLabel)
|
||||
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 324, Short.MAX_VALUE)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
|
||||
|
||||
gotoPageLabel.getAccessibleContext().setAccessibleName("");
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void pagePrevButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pagePrevButtonActionPerformed
|
||||
pagingSupport.previousPage();
|
||||
}//GEN-LAST:event_pagePrevButtonActionPerformed
|
||||
|
||||
private void pageNextButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pageNextButtonActionPerformed
|
||||
pagingSupport.nextPage();
|
||||
}//GEN-LAST:event_pageNextButtonActionPerformed
|
||||
|
||||
private void gotoPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_gotoPageTextFieldActionPerformed
|
||||
pagingSupport.gotoPage();
|
||||
}//GEN-LAST:event_gotoPageTextFieldActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel gotoPageLabel;
|
||||
private javax.swing.JTextField gotoPageTextField;
|
||||
private org.openide.explorer.view.OutlineView outlineView;
|
||||
private javax.swing.JLabel pageLabel;
|
||||
private javax.swing.JButton pageNextButton;
|
||||
private javax.swing.JLabel pageNumLabel;
|
||||
private javax.swing.JButton pagePrevButton;
|
||||
private javax.swing.JLabel pagesLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
}
|
||||
|
@ -68,7 +68,7 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="globalSettingsPanel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="currentCaseSettingsPanel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="currentSessionSettingsPanel" min="-2" max="-2" attributes="0"/>
|
||||
@ -92,7 +92,7 @@
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
@ -153,6 +153,11 @@
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="maxResultsLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="maxResultsSpinner" min="-2" pref="74" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
@ -206,6 +211,11 @@
|
||||
<Component id="fileNameTranslationColumnCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="maxResultsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="maxResultsSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
@ -407,6 +417,26 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fileNameTranslationColumnCheckboxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="maxResultsLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.maxResultsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="ViewPreferencesPanel.maxResultsLabel.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSpinner" name="maxResultsSpinner">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.SpinnerModel" editor="org.netbeans.modules.form.editors2.SpinnerModelEditor">
|
||||
<SpinnerModel initial="0" maximum="50000" minimum="0" numberType="java.lang.Integer" stepSize="10000" type="number"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="maxResultsSpinnerStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="currentCaseSettingsPanel">
|
||||
|
@ -35,6 +35,7 @@ import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
|
||||
/**
|
||||
* Panel for configuring view preferences.
|
||||
*/
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
|
||||
private final boolean immediateUpdates;
|
||||
@ -85,6 +86,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
TextTranslationService tts = TextTranslationService.getInstance();
|
||||
fileNameTranslationColumnCheckbox.setEnabled(tts.hasProvider());
|
||||
|
||||
maxResultsSpinner.setValue(UserPreferences.getResultsTablePageSize());
|
||||
|
||||
// Current Case Settings
|
||||
boolean caseIsOpen = Case.isCaseOpen();
|
||||
currentCaseSettingsPanel.setEnabled(caseIsOpen);
|
||||
@ -114,6 +117,7 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
UserPreferences.setShowOnlyCurrentUserTags(hideOtherUsersTagsCheckbox.isSelected());
|
||||
UserPreferences.setHideCentralRepoCommentsAndOccurrences(commentsOccurencesColumnsCheckbox.isSelected());
|
||||
UserPreferences.setDisplayTranslatedFileNames(fileNameTranslationColumnCheckbox.isSelected());
|
||||
UserPreferences.setResultsTablePageSize((int)maxResultsSpinner.getValue());
|
||||
|
||||
storeGroupItemsInTreeByDataSource();
|
||||
|
||||
@ -167,6 +171,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
translateTextLabel = new javax.swing.JLabel();
|
||||
commentsOccurencesColumnWrapAroundText = new javax.swing.JLabel();
|
||||
fileNameTranslationColumnCheckbox = new javax.swing.JCheckBox();
|
||||
maxResultsLabel = new javax.swing.JLabel();
|
||||
maxResultsSpinner = new javax.swing.JSpinner();
|
||||
currentCaseSettingsPanel = new javax.swing.JPanel();
|
||||
groupByDataSourceCheckbox = new javax.swing.JCheckBox();
|
||||
currentSessionSettingsPanel = new javax.swing.JPanel();
|
||||
@ -284,6 +290,16 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(maxResultsLabel, org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.text")); // NOI18N
|
||||
maxResultsLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ViewPreferencesPanel.class, "ViewPreferencesPanel.maxResultsLabel.toolTipText")); // NOI18N
|
||||
|
||||
maxResultsSpinner.setModel(new javax.swing.SpinnerNumberModel(0, 0, 50000, 10000));
|
||||
maxResultsSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
|
||||
public void stateChanged(javax.swing.event.ChangeEvent evt) {
|
||||
maxResultsSpinnerStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout globalSettingsPanelLayout = new javax.swing.GroupLayout(globalSettingsPanel);
|
||||
globalSettingsPanel.setLayout(globalSettingsPanelLayout);
|
||||
globalSettingsPanelLayout.setHorizontalGroup(
|
||||
@ -333,7 +349,11 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
.addComponent(keepCurrentViewerRadioButton)
|
||||
.addComponent(useBestViewerRadioButton)
|
||||
.addComponent(useLocalTimeRadioButton)
|
||||
.addComponent(useAnotherTimeRadioButton))))))
|
||||
.addComponent(useAnotherTimeRadioButton)))))
|
||||
.addGroup(globalSettingsPanelLayout.createSequentialGroup()
|
||||
.addComponent(maxResultsLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(maxResultsSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
globalSettingsPanelLayout.setVerticalGroup(
|
||||
@ -381,6 +401,10 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
.addComponent(translateTextLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(fileNameTranslationColumnCheckbox)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(globalSettingsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(maxResultsLabel)
|
||||
.addComponent(maxResultsSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
@ -593,6 +617,14 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
}
|
||||
}//GEN-LAST:event_fileNameTranslationColumnCheckboxActionPerformed
|
||||
|
||||
private void maxResultsSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxResultsSpinnerStateChanged
|
||||
if (immediateUpdates) {
|
||||
UserPreferences.setResultsTablePageSize((int)maxResultsSpinner.getValue());
|
||||
} else {
|
||||
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
}
|
||||
}//GEN-LAST:event_maxResultsSpinnerStateChanged
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JLabel centralRepoLabel;
|
||||
@ -613,6 +645,8 @@ public class ViewPreferencesPanel extends JPanel implements OptionsPanel {
|
||||
private javax.swing.JLabel hideSlackFilesLabel;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JRadioButton keepCurrentViewerRadioButton;
|
||||
private javax.swing.JLabel maxResultsLabel;
|
||||
private javax.swing.JSpinner maxResultsSpinner;
|
||||
private javax.swing.JLabel selectFileLabel;
|
||||
private javax.swing.JList<String> timeZoneList;
|
||||
private javax.swing.JLabel translateTextLabel;
|
||||
|
@ -34,7 +34,6 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.WeakListeners;
|
||||
@ -55,6 +54,8 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
||||
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.RefreshKeysEvent;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
|
||||
@ -164,17 +165,15 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
if (getContent().getId() == newContent.getId()) {
|
||||
// If so, refresh our children.
|
||||
try {
|
||||
Children parentsChildren = getParentNode().getChildren();
|
||||
// We only want to refresh our parents children if we are in the
|
||||
// data sources branch of the tree. The parent nodes in other
|
||||
// branches of the tree (e.g. File Types and Deleted Files) do
|
||||
// not need to be refreshed.
|
||||
if (parentsChildren instanceof ContentChildren) {
|
||||
((ContentChildren) parentsChildren).refreshChildren();
|
||||
parentsChildren.getNodesCount();
|
||||
}
|
||||
BaseChildFactory.post(getParentNode().getName(), new RefreshKeysEvent());
|
||||
} catch (NullPointerException ex) {
|
||||
// Skip
|
||||
} catch (NoSuchEventBusException ex) {
|
||||
logger.log(Level.WARNING, "Failed to post key refresh event", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,228 +18,28 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children.Keys;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
|
||||
import org.sleuthkit.autopsy.datamodel.accounts.Accounts.AccountsRootNode;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DerivedFile;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.LocalFile;
|
||||
import org.sleuthkit.datamodel.LocalDirectory;
|
||||
import org.sleuthkit.datamodel.SlackFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
|
||||
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
import org.sleuthkit.datamodel.Volume;
|
||||
|
||||
/**
|
||||
* Abstract subclass for ContentChildren and RootContentChildren implementations
|
||||
* Abstract subclass for ContentChildren implementation
|
||||
* that handles creating Nodes from Content objects.
|
||||
*/
|
||||
abstract class AbstractContentChildren<T> extends Keys<T> {
|
||||
abstract class AbstractContentChildren<T extends Content> extends BaseChildFactory<T> {
|
||||
|
||||
private final CreateSleuthkitNodeVisitor createSleuthkitNodeVisitor = new CreateSleuthkitNodeVisitor();
|
||||
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
|
||||
|
||||
/**
|
||||
* Uses lazy Content.Keys
|
||||
*/
|
||||
AbstractContentChildren() {
|
||||
/*
|
||||
* This was turned off because we were getting out of memory errors when
|
||||
* the filter nodes were hiding nodes. Turning this off seemed to help
|
||||
*/
|
||||
super(false); //don't use lazy behavior
|
||||
AbstractContentChildren(String nodeName) {
|
||||
super(nodeName, new DataSourcesKnownAndSlackFilter<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node[] createNodes(T key) {
|
||||
protected Node createNodeForKey(T key) {
|
||||
if (key instanceof SleuthkitVisitableItem) {
|
||||
return new Node[]{((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor)};
|
||||
return ((SleuthkitVisitableItem) key).accept(createSleuthkitNodeVisitor);
|
||||
} else {
|
||||
return new Node[]{((AutopsyVisitableItem) key).accept(createAutopsyNodeVisitor)};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates appropriate Node for each sub-class of Content
|
||||
*/
|
||||
public static class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default<AbstractContentNode<? extends Content>> {
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Directory drctr) {
|
||||
return new DirectoryNode(drctr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(File file) {
|
||||
return new FileNode(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Image image) {
|
||||
return new ImageNode(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Volume volume) {
|
||||
return new VolumeNode(volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LayoutFile lf) {
|
||||
return new LayoutFileNode(lf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(DerivedFile df) {
|
||||
return new LocalFileNode(df);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LocalFile lf) {
|
||||
return new LocalFileNode(lf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) {
|
||||
return new VirtualDirectoryNode(ld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LocalDirectory ld) {
|
||||
return new LocalDirectoryNode(ld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(SlackFile sf) {
|
||||
return new SlackFileNode(sf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
|
||||
return new BlackboardArtifactNode(art);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
|
||||
throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
|
||||
"AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a DisplayableItemNode for use as a subtree root node for the Autopsy
|
||||
* tree view from each type of AutopsyVisitableItem visited. There are
|
||||
* AutopsyVisitableItems for the Data Sources, Views, Results, and Reports
|
||||
* subtrees, and for the subtrees of Results (e.g., Extracted Content, Hash
|
||||
* Set Hits, etc.).
|
||||
*/
|
||||
static class CreateAutopsyNodeVisitor extends AutopsyItemVisitor.Default<AbstractNode> {
|
||||
|
||||
@Override
|
||||
public ExtractedContent.RootNode visit(ExtractedContent ec) {
|
||||
return ec.new RootNode(ec.getSleuthkitCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypesByExtension sf) {
|
||||
return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(RecentFiles rf) {
|
||||
return new RecentFilesNode(rf.getSleuthkitCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DeletedContent dc) {
|
||||
return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileSize dc) {
|
||||
return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(KeywordHits kh) {
|
||||
return kh.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(HashsetHits hh) {
|
||||
return hh.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(InterestingHits ih) {
|
||||
return ih.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(EmailExtracted ee) {
|
||||
return ee.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Tags tagsNodeKey) {
|
||||
return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DataSources i) {
|
||||
return new DataSourcesNode(i.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DataSourceGrouping datasourceGrouping) {
|
||||
return new DataSourceGroupingNode(datasourceGrouping.getDataSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Views v) {
|
||||
return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Results results) {
|
||||
return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypes ft) {
|
||||
return ft.new FileTypesNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Reports reportsItem) {
|
||||
return new Reports.ReportsListNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Accounts accountsItem) {
|
||||
return accountsItem.new AccountsRootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractNode defaultVisit(AutopsyVisitableItem di) {
|
||||
throw new UnsupportedOperationException(
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) {
|
||||
return ftByMimeTypeItem.new ByMimeTypeNode();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -22,6 +22,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.nodes.Children;
|
||||
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.openide.util.Lookup;
|
||||
@ -65,8 +66,7 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
|
||||
* @param lookup The Lookup object for the node.
|
||||
*/
|
||||
AbstractContentNode(T content, Lookup lookup) {
|
||||
//TODO consider child factory for the content children
|
||||
super(new ContentChildren(content), lookup);
|
||||
super(Children.create(new ContentChildren(content), true), lookup);
|
||||
this.content = content;
|
||||
//super.setName(ContentUtils.getSystemName(content));
|
||||
super.setName("content_" + Long.toString(content.getId())); //NON-NLS
|
||||
|
@ -135,7 +135,7 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
|
||||
protected Node createNodeForKey(Object key) {
|
||||
|
||||
if (key instanceof SleuthkitVisitableItem) {
|
||||
return ((SleuthkitVisitableItem) key).accept(new RootContentChildren.CreateSleuthkitNodeVisitor());
|
||||
return ((SleuthkitVisitableItem) key).accept(new CreateSleuthkitNodeVisitor());
|
||||
} else if (key instanceof AutopsyVisitableItem) {
|
||||
return ((AutopsyVisitableItem) key).accept(new RootContentChildren.CreateAutopsyNodeVisitor());
|
||||
}
|
||||
|
346
Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java
Normal file
346
Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.prefs.PreferenceChangeEvent;
|
||||
import java.util.stream.Collectors;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Abstract child factory that provides paging and filtering functionality to
|
||||
* subclasses.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class BaseChildFactory<T extends Content> extends ChildFactory.Detachable<T> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(BaseChildFactory.class.getName());
|
||||
|
||||
private Predicate<T> filter;
|
||||
private boolean isPageChangeEvent;
|
||||
private boolean isPageSizeChangeEvent;
|
||||
|
||||
private final PagingSupport pagingSupport;
|
||||
|
||||
/**
|
||||
* This static map is used to facilitate communication between the UI and
|
||||
* the child factory.
|
||||
*/
|
||||
private static Map<String, EventBus> nodeNameToEventBusMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Messages({
|
||||
"# {0} - node name", "BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}"
|
||||
})
|
||||
public static class NoSuchEventBusException extends Exception {
|
||||
|
||||
public NoSuchEventBusException(String nodeName) {
|
||||
super(Bundle.BaseChildFactory_NoSuchEventBusException_message(nodeName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given subscriber for the given node name. Will create the
|
||||
* event bus for the given node name if it does not exist.
|
||||
*
|
||||
* @param nodeName The name of the node.
|
||||
* @param subscriber The subscriber to register.
|
||||
*/
|
||||
public static void register(String nodeName, Object subscriber) {
|
||||
EventBus bus = nodeNameToEventBusMap.get(nodeName);
|
||||
if (bus == null) {
|
||||
bus = new EventBus(nodeName);
|
||||
nodeNameToEventBusMap.put(nodeName, bus);
|
||||
}
|
||||
bus.register(subscriber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post the given event for the given node name.
|
||||
*
|
||||
* @param nodeName The name of the node.
|
||||
* @param event The event to post.
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException
|
||||
*/
|
||||
public static void post(String nodeName, Object event) throws NoSuchEventBusException {
|
||||
EventBus bus = nodeNameToEventBusMap.get(nodeName);
|
||||
if (bus == null) {
|
||||
throw new NoSuchEventBusException(nodeName);
|
||||
}
|
||||
bus.post(event);
|
||||
}
|
||||
|
||||
public BaseChildFactory(String nodeName) {
|
||||
/**
|
||||
* Initialize a no-op filter that always returns true.
|
||||
*/
|
||||
this(nodeName, x -> true);
|
||||
}
|
||||
|
||||
public BaseChildFactory(String nodeName, Predicate<T> filter) {
|
||||
pagingSupport = new PagingSupport(nodeName);
|
||||
pagingSupport.initialize();
|
||||
isPageChangeEvent = false;
|
||||
isPageSizeChangeEvent = false;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
onAdd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
onRemove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses implement this to construct a collection of keys.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract List<T> makeKeys();
|
||||
|
||||
/**
|
||||
* Subclasses implement this to initialize any required resources.
|
||||
*/
|
||||
protected abstract void onAdd();
|
||||
|
||||
/**
|
||||
* Subclasses implement this to clean up any resources they acquired in
|
||||
* onAdd()
|
||||
*/
|
||||
protected abstract void onRemove();
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<T> toPopulate) {
|
||||
/**
|
||||
* For page change events and page size change events we simply return
|
||||
* the previously calculated set of keys, otherwise we make a new set of
|
||||
* keys.
|
||||
*/
|
||||
if (!isPageChangeEvent && !isPageSizeChangeEvent) {
|
||||
List<T> allKeys = makeKeys();
|
||||
|
||||
pagingSupport.splitKeysIntoPages(allKeys.stream().filter(filter).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
toPopulate.addAll(pagingSupport.getCurrentPage());
|
||||
|
||||
// Reset page change and page size change event flags
|
||||
isPageChangeEvent = false;
|
||||
isPageSizeChangeEvent = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event used to trigger recreation of the keys.
|
||||
*/
|
||||
public static class RefreshKeysEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Event used to let subscribers know that the user has navigated to a
|
||||
* different page.
|
||||
*/
|
||||
public static class PageChangeEvent {
|
||||
|
||||
private final int pageNumber;
|
||||
|
||||
public PageChangeEvent(int newPageNumber) {
|
||||
pageNumber = newPageNumber;
|
||||
}
|
||||
|
||||
public int getPageNumber() {
|
||||
return pageNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event used to let subscribers know that the number of pages has changed.
|
||||
*/
|
||||
public static class PageCountChangeEvent {
|
||||
|
||||
private final int pageCount;
|
||||
|
||||
public PageCountChangeEvent(int newPageCount) {
|
||||
pageCount = newPageCount;
|
||||
}
|
||||
|
||||
public int getPageCount() {
|
||||
return pageCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event used to let subscribers know that the page size has changed.
|
||||
*/
|
||||
public static class PageSizeChangeEvent {
|
||||
|
||||
private final int pageSize;
|
||||
|
||||
public PageSizeChangeEvent(int newPageSize) {
|
||||
pageSize = newPageSize;
|
||||
}
|
||||
|
||||
public int getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that supplies paging related functionality to the base child
|
||||
* factory class.
|
||||
*/
|
||||
private class PagingSupport {
|
||||
|
||||
private final String nodeName;
|
||||
private int pageSize;
|
||||
private int currentPage;
|
||||
private List<List<T>> pages;
|
||||
|
||||
/**
|
||||
* Construct PagingSupport instance for the given node name.
|
||||
*
|
||||
* @param nodeName Name of the node in the tree for which results are
|
||||
* being displayed. The node name is used to allow
|
||||
* communication between the UI and the ChildFactory via
|
||||
* an EventBus.
|
||||
*/
|
||||
PagingSupport(String nodeName) {
|
||||
currentPage = 1;
|
||||
pageSize = UserPreferences.getResultsTablePageSize();
|
||||
pages = new ArrayList<>();
|
||||
this.nodeName = nodeName;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
/**
|
||||
* Set up a change listener so we know when the user changes the
|
||||
* page size.
|
||||
*/
|
||||
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
|
||||
if (evt.getKey().equals(UserPreferences.RESULTS_TABLE_PAGE_SIZE)) {
|
||||
pageSize = UserPreferences.getResultsTablePageSize();
|
||||
}
|
||||
});
|
||||
|
||||
register(nodeName, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of keys at the current page.
|
||||
*
|
||||
* @return List of keys.
|
||||
*/
|
||||
List<T> getCurrentPage() {
|
||||
if (!pages.isEmpty()) {
|
||||
return pages.get(currentPage - 1);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the given collection of keys into pages based on page size.
|
||||
*
|
||||
* @param keys
|
||||
*/
|
||||
void splitKeysIntoPages(List<T> keys) {
|
||||
int oldPageCount = pages.size();
|
||||
|
||||
/**
|
||||
* If pageSize is set split keys into pages, otherwise create a
|
||||
* single page containing all keys.
|
||||
*/
|
||||
pages = Lists.partition(keys, pageSize > 0 ? pageSize : keys.size());
|
||||
if (pages.size() != oldPageCount) {
|
||||
try {
|
||||
// Number of pages has changed so we need to send out a notification.
|
||||
post(nodeName, new PageCountChangeEvent(pages.size()));
|
||||
} catch (NoSuchEventBusException ex) {
|
||||
logger.log(Level.WARNING, "Failed to post page change event.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives page change events from UI components and triggers a refresh
|
||||
* in the child factory.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@Subscribe
|
||||
private void subscribeToPageChange(PageChangeEvent event) {
|
||||
if (event != null) {
|
||||
currentPage = event.getPageNumber();
|
||||
isPageChangeEvent = true;
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives page size change events from UI components and triggers a
|
||||
* refresh in the child factory if necessary.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@Subscribe
|
||||
private void subscribeToPageSizeChange(PageSizeChangeEvent event) {
|
||||
if (event != null) {
|
||||
int newPageSize = event.getPageSize();
|
||||
if (pageSize == newPageSize) {
|
||||
// No change...nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
pageSize = newPageSize;
|
||||
splitKeysIntoPages(pages.stream().flatMap(List::stream).collect(Collectors.toList()));
|
||||
|
||||
currentPage = 1;
|
||||
isPageSizeChangeEvent = true;
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
private void subscribeToRefreshKeys(RefreshKeysEvent event) {
|
||||
if (event != null) {
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -43,6 +43,8 @@ ArtifactStringContent.attrsTableHeader.type=Type
|
||||
ArtifactStringContent.attrsTableHeader.value=Value
|
||||
ArtifactStringContent.failedToGetAttributes.message=Failed to get some or all attributes from case database
|
||||
ArtifactStringContent.failedToGetSourcePath.message=Failed to get source file path from case database
|
||||
# {0} - node name
|
||||
BaseChildFactory.NoSuchEventBusException.message=No event bus for node: {0}
|
||||
BlackboardArtifactNode.createSheet.artifactDetails.displayName=Result Details
|
||||
BlackboardArtifactNode.createSheet.artifactDetails.name=Result Details
|
||||
BlackboardArtifactNode.createSheet.artifactMD5.displayName=MD5 Hash
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2014 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -33,17 +33,15 @@ import org.sleuthkit.datamodel.VolumeSystem;
|
||||
/**
|
||||
* Makes the children nodes / keys for a given content object. Has knowledge
|
||||
* about the structure of the directory tree and what levels should be ignored.
|
||||
* TODO consider a ContentChildren child factory
|
||||
*/
|
||||
class ContentChildren extends AbstractContentChildren<Content> {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ContentChildren.class.getName());
|
||||
//private static final int MAX_CHILD_COUNT = 1000000;
|
||||
|
||||
private final Content parent;
|
||||
|
||||
ContentChildren(Content parent) {
|
||||
super(); //initialize lazy behavior
|
||||
super("content_" + Long.toString(parent.getId()));
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@ -90,7 +88,7 @@ class ContentChildren extends AbstractContentChildren<Content> {
|
||||
children.add(c);
|
||||
}
|
||||
} else if (c instanceof LocalDirectory) {
|
||||
LocalDirectory localDir = (LocalDirectory)c;
|
||||
LocalDirectory localDir = (LocalDirectory) c;
|
||||
if (localDir.isRoot()) {
|
||||
children.addAll(getDisplayChildren(localDir));
|
||||
} else {
|
||||
@ -104,27 +102,13 @@ class ContentChildren extends AbstractContentChildren<Content> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
super.addNotify();
|
||||
|
||||
//TODO check global settings
|
||||
//if above limit, query and return subrange
|
||||
//StopWatch s2 = new StopWatch();
|
||||
//s2.start();
|
||||
//logger.log(Level.INFO, "GETTING CHILDREN CONTENT for parent: " + parent.getName());
|
||||
List<Content> children = getDisplayChildren(parent);
|
||||
//s2.stop();
|
||||
//logger.log(Level.INFO, "GOT CHILDREN CONTENTS:" + children.size() + ", took: " + s2.getElapsedTime());
|
||||
|
||||
//limit number children
|
||||
//setKeys(children.subList(0, Math.min(children.size(), MAX_CHILD_COUNT)));
|
||||
setKeys(children);
|
||||
protected List<Content> makeKeys() {
|
||||
return getDisplayChildren(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
super.removeNotify();
|
||||
setKeys(new ArrayList<>());
|
||||
protected void onAdd() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +117,11 @@ class ContentChildren extends AbstractContentChildren<Content> {
|
||||
* them).
|
||||
*/
|
||||
void refreshChildren() {
|
||||
List<Content> children = getDisplayChildren(parent);
|
||||
setKeys(children);
|
||||
refresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DerivedFile;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.LocalDirectory;
|
||||
import org.sleuthkit.datamodel.LocalFile;
|
||||
import org.sleuthkit.datamodel.SlackFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
|
||||
import org.sleuthkit.datamodel.SleuthkitVisitableItem;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
import org.sleuthkit.datamodel.Volume;
|
||||
|
||||
/**
|
||||
* Creates appropriate Node for each sub-class of Content
|
||||
*/
|
||||
public class CreateSleuthkitNodeVisitor extends SleuthkitItemVisitor.Default<AbstractContentNode<? extends Content>> {
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Directory drctr) {
|
||||
return new DirectoryNode(drctr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(File file) {
|
||||
return new FileNode(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Image image) {
|
||||
return new ImageNode(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(Volume volume) {
|
||||
return new VolumeNode(volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LayoutFile lf) {
|
||||
return new LayoutFileNode(lf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(DerivedFile df) {
|
||||
return new LocalFileNode(df);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LocalFile lf) {
|
||||
return new LocalFileNode(lf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(VirtualDirectory ld) {
|
||||
return new VirtualDirectoryNode(ld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(LocalDirectory ld) {
|
||||
return new LocalDirectoryNode(ld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(SlackFile sf) {
|
||||
return new SlackFileNode(sf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
|
||||
return new BlackboardArtifactNode(art);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
|
||||
throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),
|
||||
"AbstractContentChildren.CreateTSKNodeVisitor.exception.noNodeMsg"));
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
|
||||
|
||||
import java.util.prefs.PreferenceChangeEvent;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterKnown;
|
||||
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterSlack;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Known and Slack filter for Data Sources section of the tree.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
class DataSourcesKnownAndSlackFilter<T extends Content> extends KnownAndSlackFilterBase<T> {
|
||||
|
||||
static {
|
||||
/**
|
||||
* Watch for user preference changes and update variables inherited from
|
||||
* our parent. The actual filtering is provided by our parent class.
|
||||
*/
|
||||
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
|
||||
if (evt.getKey().equals(UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE)) {
|
||||
filterKnown = UserPreferences.hideKnownFilesInDataSourcesTree();
|
||||
} else if (evt.getKey().equals(UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE)) {
|
||||
filterSlack = UserPreferences.hideSlackFilesInDataSourcesTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DataSourcesKnownAndSlackFilter() {
|
||||
filterKnown = UserPreferences.hideKnownFilesInDataSourcesTree();
|
||||
filterSlack = UserPreferences.hideSlackFilesInDataSourcesTree();
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
@ -56,7 +57,7 @@ public class DataSourcesNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
public DataSourcesNode(long dsObjId) {
|
||||
super(new DataSourcesNodeChildren(dsObjId), Lookups.singleton(NAME));
|
||||
super(Children.create(new DataSourcesNodeChildren(dsObjId), true), Lookups.singleton(NAME));
|
||||
displayName = (dsObjId > 0) ? NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.group_by_datasource.name") : NAME;
|
||||
init();
|
||||
}
|
||||
@ -87,7 +88,7 @@ public class DataSourcesNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
public DataSourcesNodeChildren(long dsObjId) {
|
||||
super();
|
||||
super("ds_" + Long.toString(dsObjId));
|
||||
this.currentKeys = new ArrayList<>();
|
||||
this.datasourceObjId = dsObjId;
|
||||
}
|
||||
@ -97,25 +98,24 @@ public class DataSourcesNode extends DisplayableItemNode {
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
String eventType = evt.getPropertyName();
|
||||
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
|
||||
reloadKeys();
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
|
||||
reloadKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), pcl);
|
||||
currentKeys.clear();
|
||||
setKeys(Collections.<Content>emptySet());
|
||||
}
|
||||
|
||||
private void reloadKeys() {
|
||||
@Override
|
||||
protected List<Content> makeKeys() {
|
||||
try {
|
||||
if (datasourceObjId == 0) {
|
||||
currentKeys = Case.getCurrentCaseThrows().getDataSources();
|
||||
@ -135,20 +135,11 @@ public class DataSourcesNode extends DisplayableItemNode {
|
||||
|
||||
});
|
||||
|
||||
setKeys(currentKeys);
|
||||
} catch (TskCoreException | NoCurrentCaseException | TskDataException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS
|
||||
setKeys(Collections.<Content>emptySet());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh all content keys This creates new nodes of keys have changed.
|
||||
*/
|
||||
public void refreshContentKeys() {
|
||||
for (Content key : currentKeys) {
|
||||
refreshKey(key);
|
||||
}
|
||||
return currentKeys;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -39,7 +39,6 @@ import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CasePreferences;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
@ -358,7 +357,7 @@ public class DeletedContent implements AutopsyVisitableItem {
|
||||
}
|
||||
}
|
||||
|
||||
static class DeletedContentChildren extends ChildFactory.Detachable<AbstractFile> {
|
||||
static class DeletedContentChildren extends BaseChildFactory<AbstractFile> {
|
||||
|
||||
private final SleuthkitCase skCase;
|
||||
private final DeletedContent.DeletedContentFilter filter;
|
||||
@ -368,6 +367,7 @@ public class DeletedContent implements AutopsyVisitableItem {
|
||||
private final long datasourceObjId;
|
||||
|
||||
DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o, long datasourceObjId) {
|
||||
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
|
||||
this.skCase = skCase;
|
||||
this.filter = filter;
|
||||
this.notifier = o;
|
||||
@ -376,6 +376,11 @@ public class DeletedContent implements AutopsyVisitableItem {
|
||||
|
||||
private final Observer observer = new DeletedContentChildrenObserver();
|
||||
|
||||
@Override
|
||||
protected List<AbstractFile> makeKeys() {
|
||||
return runFsQuery();
|
||||
}
|
||||
|
||||
// Cause refresh of children if there are changes
|
||||
private class DeletedContentChildrenObserver implements Observer {
|
||||
|
||||
@ -386,25 +391,19 @@ public class DeletedContent implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
if (notifier != null) {
|
||||
notifier.addObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
if (notifier != null) {
|
||||
notifier.deleteObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<AbstractFile> list) {
|
||||
list.addAll(runFsQuery());
|
||||
return true;
|
||||
}
|
||||
|
||||
static private String makeQuery(DeletedContent.DeletedContentFilter filter, long filteringDSObjId) {
|
||||
String query = "";
|
||||
switch (filter) {
|
||||
@ -440,11 +439,6 @@ public class DeletedContent implements AutopsyVisitableItem {
|
||||
|
||||
}
|
||||
|
||||
if (UserPreferences.hideKnownFilesInViewsTree()) {
|
||||
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
|
||||
+ " OR known IS NULL)"; //NON-NLS
|
||||
}
|
||||
|
||||
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
|
||||
query += " AND data_source_obj_id = " + filteringDSObjId;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012-2018 Basis Technology Corp.
|
||||
* Copyright 2012-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -42,7 +42,6 @@ import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CasePreferences;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
@ -494,41 +493,52 @@ public class EmailExtracted implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Node representing mail folder content (mail messages)
|
||||
*/
|
||||
private class MessageFactory extends ChildFactory<Long> implements Observer {
|
||||
private class MessageFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
|
||||
|
||||
private final String accountName;
|
||||
private final String folderName;
|
||||
|
||||
private MessageFactory(String accountName, String folderName) {
|
||||
super();
|
||||
super(accountName + "_" + folderName);
|
||||
this.accountName = accountName;
|
||||
this.folderName = folderName;
|
||||
emailResults.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> list) {
|
||||
list.addAll(emailResults.getArtifactIds(accountName, folderName));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(Long artifactId) {
|
||||
if (skCase == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
BlackboardArtifact artifact = skCase.getBlackboardArtifact(artifactId);
|
||||
return new BlackboardArtifactNode(artifact);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error creating mail messages nodes", ex); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
protected Node createNodeForKey(BlackboardArtifact art) {
|
||||
return new BlackboardArtifactNode(art);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
refresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<BlackboardArtifact> makeKeys() {
|
||||
List<BlackboardArtifact> keys = new ArrayList<>();
|
||||
|
||||
if (skCase != null) {
|
||||
emailResults.getArtifactIds(accountName, folderName).forEach((id) -> {
|
||||
try {
|
||||
keys.add(skCase.getBlackboardArtifact(id));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting mail messages keys", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAdd() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -423,12 +423,12 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Creates children for a given artifact type
|
||||
*/
|
||||
private class ArtifactFactory extends ChildFactory.Detachable<BlackboardArtifact> {
|
||||
private class ArtifactFactory extends BaseChildFactory<BlackboardArtifact> {
|
||||
|
||||
private BlackboardArtifact.Type type;
|
||||
|
||||
public ArtifactFactory(BlackboardArtifact.Type type) {
|
||||
super();
|
||||
super(type.getTypeName());
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@ -481,36 +481,34 @@ public class ExtractedContent implements AutopsyVisitableItem {
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
IngestManager.getInstance().addIngestJobEventListener(pcl);
|
||||
IngestManager.getInstance().addIngestModuleEventListener(pcl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
IngestManager.getInstance().removeIngestJobEventListener(pcl);
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<BlackboardArtifact> list) {
|
||||
if (skCase != null) {
|
||||
try {
|
||||
List<BlackboardArtifact> arts =
|
||||
Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) ?
|
||||
blackboard.getArtifacts(type.getTypeID(), datasourceObjId) :
|
||||
skCase.getBlackboardArtifacts(type.getTypeID());
|
||||
list.addAll(arts);
|
||||
} catch (TskException ex) {
|
||||
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(BlackboardArtifact key) {
|
||||
return new BlackboardArtifactNode(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<BlackboardArtifact> makeKeys() {
|
||||
if (skCase != null) {
|
||||
try {
|
||||
return Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)
|
||||
? blackboard.getArtifacts(type.getTypeID(), datasourceObjId)
|
||||
: skCase.getBlackboardArtifacts(type.getTypeID());
|
||||
} catch (TskException ex) {
|
||||
Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Copyright 2013-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -361,7 +361,7 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
/*
|
||||
* Makes children, which are nodes for files of a given range
|
||||
*/
|
||||
static class FileSizeChildren extends ChildFactory.Detachable<AbstractFile> {
|
||||
static class FileSizeChildren extends BaseChildFactory<AbstractFile> {
|
||||
|
||||
private final SleuthkitCase skCase;
|
||||
private final FileSizeFilter filter;
|
||||
@ -377,6 +377,7 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
* added to case
|
||||
*/
|
||||
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o, long dsObjId) {
|
||||
super(filter.getName(), new ViewsKnownAndSlackFilter<>());
|
||||
this.skCase = skCase;
|
||||
this.filter = filter;
|
||||
this.notifier = o;
|
||||
@ -385,14 +386,14 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
if (notifier != null) {
|
||||
notifier.addObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
if (notifier != null) {
|
||||
notifier.deleteObserver(observer);
|
||||
}
|
||||
@ -400,6 +401,11 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
|
||||
private final Observer observer = new FileSizeChildrenObserver();
|
||||
|
||||
@Override
|
||||
protected List<AbstractFile> makeKeys() {
|
||||
return runFsQuery();
|
||||
}
|
||||
|
||||
// Cause refresh of children if there are changes
|
||||
private class FileSizeChildrenObserver implements Observer {
|
||||
|
||||
@ -409,12 +415,6 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<AbstractFile> list) {
|
||||
list.addAll(runFsQuery());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String makeQuery(FileSizeFilter filter, long filteringDSObjId) {
|
||||
String query;
|
||||
switch (filter) {
|
||||
@ -436,17 +436,6 @@ public class FileSize implements AutopsyVisitableItem {
|
||||
// Ignore unallocated block files.
|
||||
query = query + " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS.getFileType() + ")"; //NON-NLS
|
||||
|
||||
// Hide known files if indicated in the user preferences.
|
||||
if(UserPreferences.hideKnownFilesInViewsTree()) {
|
||||
query += " AND (known != " + TskData.FileKnown.KNOWN.getFileKnownValue() //NON-NLS
|
||||
+ " OR known IS NULL)"; //NON-NLS
|
||||
}
|
||||
|
||||
// Hide slack files if indicated in the user preferences.
|
||||
if(UserPreferences.hideSlackFilesInViewsTree()) {
|
||||
query += " AND (type != " + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType() + ")"; //NON-NLS
|
||||
}
|
||||
|
||||
// filter by datasource if indicated in case preferences
|
||||
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
|
||||
query += " AND data_source_obj_id = " + filteringDSObjId;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -292,7 +293,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
* should refresh
|
||||
*/
|
||||
FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) {
|
||||
super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o), true),
|
||||
super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o, filter.getDisplayName()), true),
|
||||
Lookups.singleton(filter.getDisplayName()));
|
||||
this.filter = filter;
|
||||
super.setName(filter.getDisplayName());
|
||||
@ -377,7 +378,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Child node factory for a specific file type - does the database query.
|
||||
*/
|
||||
private class FileExtensionNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer {
|
||||
private class FileExtensionNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
|
||||
|
||||
private final SleuthkitCase skCase;
|
||||
private final FileTypesByExtension.SearchFilterInterface filter;
|
||||
@ -390,22 +391,22 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
* @param o Observable that will notify when there could be new
|
||||
* data to display
|
||||
*/
|
||||
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) {
|
||||
super();
|
||||
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) {
|
||||
super(nodeName, new ViewsKnownAndSlackFilter<>());
|
||||
this.filter = filter;
|
||||
this.skCase = skCase;
|
||||
notifier = o;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
if (notifier != null) {
|
||||
notifier.addObserver(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
if (notifier != null) {
|
||||
notifier.deleteObserver(this);
|
||||
}
|
||||
@ -417,19 +418,19 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<FileTypesKey> list) {
|
||||
try {
|
||||
list.addAll(skCase.findAllFilesWhere(createQuery(filter))
|
||||
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
|
||||
}
|
||||
return true;
|
||||
protected Node createNodeForKey(FileTypesKey key) {
|
||||
return key.accept(new FileTypes.FileNodeCreationVisitor());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(FileTypesKey key) {
|
||||
return key.accept(new FileTypes.FileNodeCreationVisitor());
|
||||
protected List<FileTypesKey> makeKeys() {
|
||||
try {
|
||||
return skCase.findAllFilesWhere(createQuery(filter))
|
||||
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -445,27 +445,16 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
* files that match MimeType which is represented by this position in the
|
||||
* tree.
|
||||
*/
|
||||
private class MediaSubTypeNodeChildren extends ChildFactory.Detachable<FileTypesKey> implements Observer {
|
||||
private class MediaSubTypeNodeChildren extends BaseChildFactory<FileTypesKey> implements Observer {
|
||||
|
||||
private final String mimeType;
|
||||
|
||||
private MediaSubTypeNodeChildren(String mimeType) {
|
||||
super();
|
||||
super(mimeType, new ViewsKnownAndSlackFilter<>());
|
||||
addObserver(this);
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<FileTypesKey> list) {
|
||||
try {
|
||||
list.addAll(skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
|
||||
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList())); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
refresh(true);
|
||||
@ -475,5 +464,26 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi
|
||||
protected Node createNodeForKey(FileTypesKey key) {
|
||||
return key.accept(new FileTypes.FileNodeCreationVisitor());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<FileTypesKey> makeKeys() {
|
||||
try {
|
||||
return skCase.findAllFilesWhere(createBaseWhereExpr() + " AND mime_type = '" + mimeType + "'")
|
||||
.stream().map(f -> new FileTypesKey(f)).collect(Collectors.toList()); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAdd() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -44,7 +44,6 @@ import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CasePreferences;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
@ -378,60 +377,53 @@ public class HashsetHits implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Creates the nodes for the hits in a given set.
|
||||
*/
|
||||
private class HitFactory extends ChildFactory.Detachable<Long> implements Observer {
|
||||
private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
|
||||
|
||||
private String hashsetName;
|
||||
private Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
|
||||
|
||||
private HitFactory(String hashsetName) {
|
||||
super();
|
||||
super(hashsetName);
|
||||
this.hashsetName = hashsetName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addNotify() {
|
||||
protected void onAdd() {
|
||||
hashsetResults.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNotify() {
|
||||
protected void onRemove() {
|
||||
hashsetResults.deleteObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> list) {
|
||||
|
||||
if (skCase == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
|
||||
try {
|
||||
if (!artifactHits.containsKey(id)) {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
|
||||
artifactHits.put(id, art);
|
||||
}
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
// Adding all keys at once is more efficient than adding one at a
|
||||
// time because Netbeans triggers internal processing each time an
|
||||
// element is added to the list.
|
||||
list.addAll(artifactHits.keySet());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(Long id) {
|
||||
BlackboardArtifact art = artifactHits.get(id);
|
||||
return (null == art) ? null : new BlackboardArtifactNode(art);
|
||||
protected Node createNodeForKey(BlackboardArtifact key) {
|
||||
return new BlackboardArtifactNode(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
refresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<BlackboardArtifact> makeKeys() {
|
||||
if (skCase != null) {
|
||||
|
||||
hashsetResults.getArtifactIds(hashsetName).forEach((id) -> {
|
||||
try {
|
||||
if (!artifactHits.containsKey(id)) {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
|
||||
artifactHits.put(id, art);
|
||||
}
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
return new ArrayList<>(artifactHits.values());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -28,7 +28,6 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
@ -47,6 +46,7 @@ import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
|
||||
|
||||
/**
|
||||
* This class is used to represent the "Node" for the image. The children of
|
||||
@ -224,18 +224,23 @@ public class ImageNode extends AbstractContentNode<Image> {
|
||||
if (parent != null) {
|
||||
// Is this a new carved file?
|
||||
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
|
||||
// Was this new carved file produced from this image?
|
||||
if (parent.getParent().getId() == getContent().getId()) {
|
||||
Children children = getChildren();
|
||||
if (children != null) {
|
||||
((ContentChildren) children).refreshChildren();
|
||||
children.getNodesCount();
|
||||
// Is this new carved file for this data source?
|
||||
if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
|
||||
// Find the image (if any) associated with the new content and
|
||||
// trigger a refresh if it matches the image wrapped by this node.
|
||||
while ((parent = parent.getParent()) != null) {
|
||||
if (parent.getId() == getContent().getId()) {
|
||||
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
// Do nothing.
|
||||
} catch (NoSuchEventBusException ex) {
|
||||
logger.log(Level.WARNING, "Failed to post key refresh event.", ex); // NON-NLS
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
if (evt.getNewValue() == null) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -449,51 +449,57 @@ public class InterestingHits implements AutopsyVisitableItem {
|
||||
}
|
||||
}
|
||||
|
||||
private class HitFactory extends ChildFactory<Long> implements Observer {
|
||||
private class HitFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
|
||||
|
||||
private final String setName;
|
||||
private final String typeName;
|
||||
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
|
||||
|
||||
private HitFactory(String setName, String typeName) {
|
||||
super();
|
||||
super(typeName);
|
||||
this.setName = setName;
|
||||
this.typeName = typeName;
|
||||
interestingResults.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> list) {
|
||||
protected List<BlackboardArtifact> makeKeys() {
|
||||
|
||||
if (skCase == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
|
||||
try {
|
||||
if (!artifactHits.containsKey(id)) {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
|
||||
artifactHits.put(id, art);
|
||||
if (skCase != null) {
|
||||
interestingResults.getArtifactIds(setName, typeName).forEach((id) -> {
|
||||
try {
|
||||
if (!artifactHits.containsKey(id)) {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
|
||||
artifactHits.put(id, art);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
list.addAll(artifactHits.keySet());
|
||||
|
||||
return true;
|
||||
return new ArrayList<>(artifactHits.values());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(Long l) {
|
||||
BlackboardArtifact art = artifactHits.get(l);
|
||||
return (null == art) ? null : new BlackboardArtifactNode(art);
|
||||
protected Node createNodeForKey(BlackboardArtifact art) {
|
||||
return new BlackboardArtifactNode(art);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
refresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAdd() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,6 +25,7 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@ -34,7 +35,6 @@ import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
@ -46,7 +46,6 @@ import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.CasePreferences;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
@ -85,7 +84,6 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
*/
|
||||
private static final String DEFAULT_INSTANCE_NAME = "DEFAULT_INSTANCE_NAME";
|
||||
|
||||
|
||||
/**
|
||||
* query attributes table for the ones that we need for the tree
|
||||
*/
|
||||
@ -109,7 +107,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param skCase Case DB
|
||||
* @param skCase Case DB
|
||||
*/
|
||||
KeywordHits(SleuthkitCase skCase) {
|
||||
this(skCase, 0);
|
||||
@ -118,7 +116,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param skCase Case DB
|
||||
* @param skCase Case DB
|
||||
* @param objId Object id of the data source
|
||||
*
|
||||
*/
|
||||
@ -324,7 +322,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
|
||||
String queryStr = KEYWORD_HIT_ATTRIBUTES_QUERY;
|
||||
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
|
||||
queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
|
||||
queryStr += " AND blackboard_artifacts.data_source_obj_id = " + datasourceObjId;
|
||||
}
|
||||
|
||||
try (CaseDbQuery dbQuery = skCase.executeQuery(queryStr)) {
|
||||
@ -510,9 +508,11 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
private abstract class KWHitsNodeBase extends DisplayableItemNode implements Observer {
|
||||
private String displayName;
|
||||
|
||||
private KWHitsNodeBase(Children children, Lookup lookup) {
|
||||
private KWHitsNodeBase(Children children, Lookup lookup, String displayName) {
|
||||
super(children, lookup);
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
private KWHitsNodeBase(Children children) {
|
||||
@ -530,7 +530,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
final void updateDisplayName() {
|
||||
super.setDisplayName(getName() + " (" + countTotalDescendants() + ")");
|
||||
super.setDisplayName(displayName + " (" + countTotalDescendants() + ")");
|
||||
}
|
||||
|
||||
abstract int countTotalDescendants();
|
||||
@ -545,7 +545,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
private final String listName;
|
||||
|
||||
private ListNode(String listName) {
|
||||
super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName));
|
||||
super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName), listName);
|
||||
super.setName(listName);
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
|
||||
this.listName = listName;
|
||||
@ -631,6 +631,24 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ChildFactory object for the given set name and keyword.
|
||||
*
|
||||
* The type of ChildFactory we create is based on whether the node
|
||||
* represents a regular expression keyword search or not. For regular
|
||||
* expression keyword searches there will be an extra layer in the tree that
|
||||
* represents each of the individual terms found by the regular expression.
|
||||
* E.g., for an email regular expression search there will be a node in the
|
||||
* tree for every email address hit.
|
||||
*/
|
||||
ChildFactory<?> createChildFactory(String setName, String keyword) {
|
||||
if (isOnlyDefaultInstance(keywordResults.getKeywordInstances(setName, keyword))) {
|
||||
return new HitsFactory(setName, keyword, DEFAULT_INSTANCE_NAME);
|
||||
} else {
|
||||
return new RegExpInstancesFactory(setName, keyword);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the search term or regexp that user searched for
|
||||
*/
|
||||
@ -640,8 +658,16 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
private final String keyword;
|
||||
|
||||
private TermNode(String setName, String keyword) {
|
||||
super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword));
|
||||
super.setName(keyword);
|
||||
super(Children.create(createChildFactory(setName, keyword), true), Lookups.singleton(keyword), keyword);
|
||||
|
||||
/**
|
||||
* We differentiate between the programmatic name and the display
|
||||
* name. The programmatic name is used to create an association with
|
||||
* an event bus and must be the same as the node name passed by our
|
||||
* ChildFactory to it's parent constructor. See the HitsFactory
|
||||
* constructor for an example.
|
||||
*/
|
||||
super.setName(setName + "_" + keyword);
|
||||
this.setName = setName;
|
||||
this.keyword = keyword;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
|
||||
@ -694,45 +720,11 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows us to pass in either longs or strings as they keys for different
|
||||
* types of nodes at the same level. Probably a better way to do this, but
|
||||
* it works.
|
||||
*/
|
||||
private class RegExpInstanceKey {
|
||||
|
||||
private final boolean isRegExp;
|
||||
private String strKey;
|
||||
private Long longKey;
|
||||
|
||||
RegExpInstanceKey(String key) {
|
||||
isRegExp = true;
|
||||
strKey = key;
|
||||
}
|
||||
|
||||
RegExpInstanceKey(Long key) {
|
||||
isRegExp = false;
|
||||
longKey = key;
|
||||
}
|
||||
|
||||
boolean isRegExp() {
|
||||
return isRegExp;
|
||||
}
|
||||
|
||||
Long getIdKey() {
|
||||
return longKey;
|
||||
}
|
||||
|
||||
String getRegExpKey() {
|
||||
return strKey;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the nodes for a given regexp that represent the specific terms
|
||||
* that were found
|
||||
*/
|
||||
private class RegExpInstancesFactory extends DetachableObserverChildFactory<RegExpInstanceKey> {
|
||||
private class RegExpInstancesFactory extends DetachableObserverChildFactory<String> {
|
||||
|
||||
private final String keyword;
|
||||
private final String setName;
|
||||
@ -744,33 +736,15 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<RegExpInstanceKey> list) {
|
||||
List<String> instances = keywordResults.getKeywordInstances(setName, keyword);
|
||||
// The keys are different depending on what we are displaying.
|
||||
// regexp get another layer to show instances.
|
||||
// Exact/substring matches don't.
|
||||
if (isOnlyDefaultInstance(instances)) {
|
||||
list.addAll(keywordResults.getArtifactIds(setName, keyword, DEFAULT_INSTANCE_NAME).stream()
|
||||
.map(RegExpInstanceKey::new)
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
list.addAll(instances.stream()
|
||||
.map(RegExpInstanceKey::new)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
protected boolean createKeys(List<String> list) {
|
||||
list.addAll(keywordResults.getKeywordInstances(setName, keyword));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(RegExpInstanceKey key) {
|
||||
if (key.isRegExp()) {
|
||||
return new RegExpInstanceNode(setName, keyword, key.getRegExpKey());
|
||||
} else {
|
||||
// if it isn't a regexp, then skip the 'instance' layer of the tree
|
||||
return createBlackboardArtifactNode(key.getIdKey());
|
||||
}
|
||||
protected Node createNodeForKey(String key) {
|
||||
return new RegExpInstanceNode(setName, keyword, key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -783,8 +757,16 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
private final String instance;
|
||||
|
||||
private RegExpInstanceNode(String setName, String keyword, String instance) {
|
||||
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance));
|
||||
super.setName(instance); //the instance represents the name of the keyword hit at this point as the keyword is the regex
|
||||
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(instance), instance);
|
||||
|
||||
/**
|
||||
* We differentiate between the programmatic name and the display
|
||||
* name. The programmatic name is used to create an association with
|
||||
* an event bus and must be the same as the node name passed by our
|
||||
* ChildFactory to it's parent constructor. See the HitsFactory
|
||||
* constructor for an example.
|
||||
*/
|
||||
super.setName(setName + "_" + keyword + "_" + instance);
|
||||
this.setName = setName;
|
||||
this.keyword = keyword;
|
||||
this.instance = instance;
|
||||
@ -837,7 +819,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
/**
|
||||
* Create a blackboard node for the given Keyword Hit artifact
|
||||
*
|
||||
* @param artifactId
|
||||
* @param art
|
||||
*
|
||||
* @return Node or null on error
|
||||
*/
|
||||
@ -850,81 +832,110 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
"KeywordHits.createNodeForKey.chgTime.name=ChangeTime",
|
||||
"KeywordHits.createNodeForKey.chgTime.displayName=Change Time",
|
||||
"KeywordHits.createNodeForKey.chgTime.desc=Change Time"})
|
||||
private BlackboardArtifactNode createBlackboardArtifactNode(Long artifactId) {
|
||||
private BlackboardArtifactNode createBlackboardArtifactNode(BlackboardArtifact art) {
|
||||
if (skCase == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
|
||||
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
|
||||
// The associated file should be available through the Lookup that
|
||||
// gets created when the BlackboardArtifactNode is constructed.
|
||||
AbstractFile file = n.getLookup().lookup(AbstractFile.class);
|
||||
if (file == null) {
|
||||
try {
|
||||
file = skCase.getAbstractFileById(art.getObjectID());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS
|
||||
return n;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* It is possible to get a keyword hit on artifacts generated for
|
||||
* the underlying image in which case MAC times are not
|
||||
* available/applicable/useful.
|
||||
*/
|
||||
if (file == null) {
|
||||
BlackboardArtifactNode n = new BlackboardArtifactNode(art); //NON-NLS
|
||||
|
||||
// The associated file should be available through the Lookup that
|
||||
// gets created when the BlackboardArtifactNode is constructed.
|
||||
AbstractFile file = n.getLookup().lookup(AbstractFile.class);
|
||||
if (file == null) {
|
||||
try {
|
||||
file = skCase.getAbstractFileById(art.getObjectID());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren", ex); //NON-NLS
|
||||
return n;
|
||||
}
|
||||
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_modTime_name(),
|
||||
KeywordHits_createNodeForKey_modTime_displayName(),
|
||||
KeywordHits_createNodeForKey_modTime_desc(),
|
||||
ContentUtils.getStringTime(file.getMtime(), file)));
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_accessTime_name(),
|
||||
KeywordHits_createNodeForKey_accessTime_displayName(),
|
||||
KeywordHits_createNodeForKey_accessTime_desc(),
|
||||
ContentUtils.getStringTime(file.getAtime(), file)));
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_chgTime_name(),
|
||||
KeywordHits_createNodeForKey_chgTime_displayName(),
|
||||
KeywordHits_createNodeForKey_chgTime_desc(),
|
||||
ContentUtils.getStringTime(file.getCtime(), file)));
|
||||
return n;
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
/*
|
||||
* It is possible to get a keyword hit on artifacts generated for the
|
||||
* underlying image in which case MAC times are not
|
||||
* available/applicable/useful.
|
||||
*/
|
||||
if (file == null) {
|
||||
return n;
|
||||
}
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_modTime_name(),
|
||||
KeywordHits_createNodeForKey_modTime_displayName(),
|
||||
KeywordHits_createNodeForKey_modTime_desc(),
|
||||
ContentUtils.getStringTime(file.getMtime(), file)));
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_accessTime_name(),
|
||||
KeywordHits_createNodeForKey_accessTime_displayName(),
|
||||
KeywordHits_createNodeForKey_accessTime_desc(),
|
||||
ContentUtils.getStringTime(file.getAtime(), file)));
|
||||
n.addNodeProperty(new NodeProperty<>(
|
||||
KeywordHits_createNodeForKey_chgTime_name(),
|
||||
KeywordHits_createNodeForKey_chgTime_displayName(),
|
||||
KeywordHits_createNodeForKey_chgTime_desc(),
|
||||
ContentUtils.getStringTime(file.getCtime(), file)));
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates nodes for individual files that had hits
|
||||
*/
|
||||
private class HitsFactory extends DetachableObserverChildFactory<Long> {
|
||||
private class HitsFactory extends BaseChildFactory<BlackboardArtifact> implements Observer {
|
||||
|
||||
private final String keyword;
|
||||
private final String setName;
|
||||
private final String instance;
|
||||
private final Map<Long, BlackboardArtifact> artifactHits = new HashMap<>();
|
||||
|
||||
private HitsFactory(String setName, String keyword, String instance) {
|
||||
super();
|
||||
/**
|
||||
* The node name passed to the parent constructor will consist of
|
||||
* the set name, keyword and optionally the instance name (in the
|
||||
* case of regular expression hits. This name must match the name
|
||||
* set in the TermNode or RegExpInstanceNode constructors.
|
||||
*/
|
||||
super(setName + "_" + keyword + (DEFAULT_INSTANCE_NAME.equals(instance) ? "" : "_" + instance));
|
||||
this.setName = setName;
|
||||
this.keyword = keyword;
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> list) {
|
||||
list.addAll(keywordResults.getArtifactIds(setName, keyword, instance));
|
||||
return true;
|
||||
protected List<BlackboardArtifact> makeKeys() {
|
||||
if (skCase != null) {
|
||||
keywordResults.getArtifactIds(setName, keyword, instance).forEach((id) -> {
|
||||
try {
|
||||
if (!artifactHits.containsKey(id)) {
|
||||
BlackboardArtifact art = skCase.getBlackboardArtifact(id);
|
||||
artifactHits.put(id, art);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "TSK Exception occurred", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
return new ArrayList<>(artifactHits.values());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node createNodeForKey(Long artifactId) {
|
||||
return createBlackboardArtifactNode(artifactId);
|
||||
protected Node createNodeForKey(BlackboardArtifact art) {
|
||||
return createBlackboardArtifactNode(art);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAdd() {
|
||||
keywordResults.addObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove() {
|
||||
keywordResults.deleteObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Observable o, Object arg) {
|
||||
refresh(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Predicate that can be used to filter known and/or slack files from
|
||||
* Content collections based on user preferences.
|
||||
*/
|
||||
abstract class KnownAndSlackFilterBase<T extends Content> implements Predicate<T> {
|
||||
protected static boolean filterKnown;
|
||||
protected static boolean filterSlack;
|
||||
|
||||
@Override
|
||||
public boolean test(T t) {
|
||||
AbstractFile af = null;
|
||||
|
||||
if (t instanceof AbstractFile) {
|
||||
af = (AbstractFile) t;
|
||||
}
|
||||
|
||||
if (af != null) {
|
||||
if (af.getKnown() == TskData.FileKnown.KNOWN && filterKnown) {
|
||||
return false;
|
||||
}
|
||||
if (af.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) && filterSlack) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -20,14 +20,20 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
|
||||
|
||||
/**
|
||||
* Children implementation for the root node of a ContentNode tree. Accepts a
|
||||
* list of root Content objects for the tree.
|
||||
*/
|
||||
public class RootContentChildren extends AbstractContentChildren<Object> {
|
||||
public class RootContentChildren extends Children.Keys<Object> {
|
||||
|
||||
private final Collection<? extends Object> contentKeys;
|
||||
private final CreateAutopsyNodeVisitor createAutopsyNodeVisitor = new CreateAutopsyNodeVisitor();
|
||||
|
||||
/**
|
||||
* @param contentKeys root Content objects for the Node tree
|
||||
@ -56,4 +62,120 @@ public class RootContentChildren extends AbstractContentChildren<Object> {
|
||||
public void refreshContentKeys() {
|
||||
contentKeys.forEach(this::refreshKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node[] createNodes(Object key) {
|
||||
if (key instanceof AutopsyVisitableItem) {
|
||||
return new Node[] {((AutopsyVisitableItem)key).accept(createAutopsyNodeVisitor)};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a DisplayableItemNode for use as a subtree root node for the Autopsy
|
||||
* tree view from each type of AutopsyVisitableItem visited. There are
|
||||
* AutopsyVisitableItems for the Data Sources, Views, Results, and Reports
|
||||
* subtrees, and for the subtrees of Results (e.g., Extracted Content, Hash
|
||||
* Set Hits, etc.).
|
||||
*/
|
||||
static class CreateAutopsyNodeVisitor extends AutopsyItemVisitor.Default<AbstractNode> {
|
||||
|
||||
@Override
|
||||
public ExtractedContent.RootNode visit(ExtractedContent ec) {
|
||||
return ec.new RootNode(ec.getSleuthkitCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypesByExtension sf) {
|
||||
return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(RecentFiles rf) {
|
||||
return new RecentFilesNode(rf.getSleuthkitCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DeletedContent dc) {
|
||||
return new DeletedContent.DeletedContentsNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileSize dc) {
|
||||
return new FileSize.FileSizeRootNode(dc.getSleuthkitCase(), dc.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(KeywordHits kh) {
|
||||
return kh.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(HashsetHits hh) {
|
||||
return hh.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(InterestingHits ih) {
|
||||
return ih.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(EmailExtracted ee) {
|
||||
return ee.new RootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Tags tagsNodeKey) {
|
||||
return tagsNodeKey.new RootNode(tagsNodeKey.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DataSources i) {
|
||||
return new DataSourcesNode(i.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(DataSourceGrouping datasourceGrouping) {
|
||||
return new DataSourceGroupingNode(datasourceGrouping.getDataSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Views v) {
|
||||
return new ViewsNode(v.getSleuthkitCase(), v.filteringDataSourceObjId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Results results) {
|
||||
return new ResultsNode(results.getSleuthkitCase(), results.filteringDataSourceObjId() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypes ft) {
|
||||
return ft.new FileTypesNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Reports reportsItem) {
|
||||
return new Reports.ReportsListNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(Accounts accountsItem) {
|
||||
return accountsItem.new AccountsRootNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractNode defaultVisit(AutopsyVisitableItem di) {
|
||||
throw new UnsupportedOperationException(
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) {
|
||||
return ftByMimeTypeItem.new ByMimeTypeNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.datamodel;
|
||||
|
||||
import java.util.prefs.PreferenceChangeEvent;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterKnown;
|
||||
import static org.sleuthkit.autopsy.datamodel.KnownAndSlackFilterBase.filterSlack;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Known and Slack filter for Views section of the tree.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
class ViewsKnownAndSlackFilter<T extends Content> extends KnownAndSlackFilterBase<T> {
|
||||
|
||||
static {
|
||||
/**
|
||||
* Watch for user preference changes and update variables inherited from
|
||||
* our parent. The actual filtering is provided by our parent class.
|
||||
*/
|
||||
UserPreferences.addChangeListener((PreferenceChangeEvent evt) -> {
|
||||
if (evt.getKey().equals(UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE)) {
|
||||
filterKnown = UserPreferences.hideKnownFilesInViewsTree();
|
||||
} else if (evt.getKey().equals(UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE)) {
|
||||
filterSlack = UserPreferences.hideSlackFilesInViewsTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ViewsKnownAndSlackFilter() {
|
||||
filterKnown = UserPreferences.hideKnownFilesInViewsTree();
|
||||
filterSlack = UserPreferences.hideSlackFilesInViewsTree();
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2014 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,11 +23,13 @@ import java.beans.PropertyChangeListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
|
||||
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
@ -43,6 +45,7 @@ import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
|
||||
* root directory of a file system
|
||||
*/
|
||||
public class VolumeNode extends AbstractContentNode<Volume> {
|
||||
private static final Logger logger = Logger.getLogger(VolumeNode.class.getName());
|
||||
|
||||
/**
|
||||
* Helper so that the display name and the name used in building the path
|
||||
@ -105,18 +108,23 @@ public class VolumeNode extends AbstractContentNode<Volume> {
|
||||
if (parent != null) {
|
||||
// Is this a new carved file?
|
||||
if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) {
|
||||
// Was this new carved file produced from this volume?
|
||||
if (parent.getParent().getId() == getContent().getId()) {
|
||||
Children children = getChildren();
|
||||
if (children != null) {
|
||||
((ContentChildren) children).refreshChildren();
|
||||
children.getNodesCount();
|
||||
// Is this new carved file for this data source?
|
||||
if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) {
|
||||
// Find the volume (if any) associated with the new content and
|
||||
// trigger a refresh if it matches the volume wrapped by this node.
|
||||
while ((parent = parent.getParent()) != null) {
|
||||
if (parent.getId() == getContent().getId()) {
|
||||
BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
// Do nothing.
|
||||
} catch (NoSuchEventBusException ex) {
|
||||
logger.log(Level.WARNING, eventType, ex);
|
||||
}
|
||||
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
|
||||
if (evt.getNewValue() == null) {
|
||||
|
@ -26,8 +26,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.PreferenceChangeEvent;
|
||||
import java.util.prefs.PreferenceChangeListener;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
@ -41,7 +39,6 @@ import org.sleuthkit.autopsy.actions.AddBlackboardArtifactTagAction;
|
||||
import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileBlackboardArtifactTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
|
||||
@ -80,7 +77,6 @@ import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.LocalFile;
|
||||
import org.sleuthkit.datamodel.LocalDirectory;
|
||||
import org.sleuthkit.datamodel.SlackFile;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
@ -96,33 +92,6 @@ public class DataResultFilterNode extends FilterNode {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DataResultFilterNode.class.getName());
|
||||
|
||||
private static boolean filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
|
||||
private static boolean filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
|
||||
private static boolean filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
|
||||
private static boolean filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
|
||||
|
||||
static {
|
||||
UserPreferences.addChangeListener(new PreferenceChangeListener() {
|
||||
@Override
|
||||
public void preferenceChange(PreferenceChangeEvent evt) {
|
||||
switch (evt.getKey()) {
|
||||
case UserPreferences.HIDE_KNOWN_FILES_IN_DATA_SRCS_TREE:
|
||||
filterKnownFromDataSources = UserPreferences.hideKnownFilesInDataSourcesTree();
|
||||
break;
|
||||
case UserPreferences.HIDE_KNOWN_FILES_IN_VIEWS_TREE:
|
||||
filterKnownFromViews = UserPreferences.hideKnownFilesInViewsTree();
|
||||
break;
|
||||
case UserPreferences.HIDE_SLACK_FILES_IN_DATA_SRCS_TREE:
|
||||
filterSlackFromDataSources = UserPreferences.hideSlackFilesInDataSourcesTree();
|
||||
break;
|
||||
case UserPreferences.HIDE_SLACK_FILES_IN_VIEWS_TREE:
|
||||
filterSlackFromViews = UserPreferences.hideSlackFilesInViewsTree();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static private final DisplayableItemNodeVisitor<List<Action>> getActionsDIV = new GetPopupActionsDisplayableItemNodeVisitor();
|
||||
private final DisplayableItemNodeVisitor<AbstractAction> getPreferredActionsDIV = new GetPreferredActionsDisplayableItemNodeVisitor();
|
||||
|
||||
@ -155,24 +124,6 @@ public class DataResultFilterNode extends FilterNode {
|
||||
this.sourceEm = em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a node used to wrap another node before passing it to the
|
||||
* result viewers. The wrapper node defines the actions associated with the
|
||||
* wrapped node and may filter out some of its children.
|
||||
*
|
||||
* @param node The node to wrap.
|
||||
* @param em The ExplorerManager for the component that is creating
|
||||
* the node.
|
||||
* @param filterKnown Whether or not to filter out children that represent
|
||||
* known files.
|
||||
* @param filterSlack Whether or not to filter out children that represent
|
||||
* virtual slack space files.
|
||||
*/
|
||||
private DataResultFilterNode(Node node, ExplorerManager em, boolean filterKnown, boolean filterSlack) {
|
||||
super(node, new DataResultFilterChildren(node, em, filterKnown, filterSlack));
|
||||
this.sourceEm = em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Right click action for the nodes that we want to pass to the directory
|
||||
* table and the output view.
|
||||
@ -294,10 +245,7 @@ public class DataResultFilterNode extends FilterNode {
|
||||
private static class DataResultFilterChildren extends FilterNode.Children {
|
||||
|
||||
private final ExplorerManager sourceEm;
|
||||
|
||||
private boolean filterKnown;
|
||||
private boolean filterSlack;
|
||||
private boolean filterArtifacts; // display message artifacts in the DataSource subtree
|
||||
private final boolean filterArtifacts; // display message artifacts in the DataSource subtree
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
@ -305,46 +253,13 @@ public class DataResultFilterNode extends FilterNode {
|
||||
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) {
|
||||
super(arg);
|
||||
|
||||
this.filterArtifacts = false;
|
||||
switch (SelectionContext.getSelectionContext(arg)) {
|
||||
case DATA_SOURCES:
|
||||
filterSlack = filterSlackFromDataSources;
|
||||
filterKnown = filterKnownFromDataSources;
|
||||
filterArtifacts = true;
|
||||
break;
|
||||
case VIEWS:
|
||||
filterSlack = filterSlackFromViews;
|
||||
filterKnown = filterKnownFromViews;
|
||||
break;
|
||||
default:
|
||||
filterSlack = false;
|
||||
filterKnown = false;
|
||||
break;
|
||||
}
|
||||
this.sourceEm = sourceEm;
|
||||
}
|
||||
filterArtifacts = SelectionContext.getSelectionContext(arg).equals(SelectionContext.DATA_SOURCES);
|
||||
|
||||
private DataResultFilterChildren(Node arg, ExplorerManager sourceEm, boolean filterKnown, boolean filterSlack) {
|
||||
super(arg);
|
||||
this.filterKnown = filterKnown;
|
||||
this.filterSlack = filterSlack;
|
||||
this.sourceEm = sourceEm;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node[] createNodes(Node key) {
|
||||
AbstractFile file = key.getLookup().lookup(AbstractFile.class);
|
||||
if (file != null) {
|
||||
if (filterKnown && (file.getKnown() == TskData.FileKnown.KNOWN)) {
|
||||
// Filter out child nodes that represent known files
|
||||
return new Node[]{};
|
||||
}
|
||||
if (filterSlack && file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) {
|
||||
// Filter out child nodes that represent slack files
|
||||
return new Node[]{};
|
||||
}
|
||||
}
|
||||
|
||||
// filter out all non-message artifacts, if displaying the results from the Data Source tree
|
||||
BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class);
|
||||
if (art != null
|
||||
@ -354,9 +269,8 @@ public class DataResultFilterNode extends FilterNode {
|
||||
return new Node[]{};
|
||||
}
|
||||
|
||||
return new Node[]{new DataResultFilterNode(key, sourceEm, filterKnown, filterSlack)};
|
||||
return new Node[]{new DataResultFilterNode(key, sourceEm)};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@NbBundle.Messages("DataResultFilterNode.viewSourceArtifact.text=View Source Result")
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -22,7 +22,7 @@ import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataContentTopComponent;
|
||||
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
|
||||
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
|
||||
import org.sleuthkit.autopsy.datamodel.CreateSleuthkitNodeVisitor;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.Content;
|
||||
*/
|
||||
class ViewAssociatedContentAction extends AbstractAction {
|
||||
|
||||
private Content content;
|
||||
private final Content content;
|
||||
|
||||
public ViewAssociatedContentAction(String title, BlackboardArtifactNode node) {
|
||||
super(title);
|
||||
@ -39,6 +39,6 @@ class ViewAssociatedContentAction extends AbstractAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
DataContentTopComponent.getDefault().setNode(content.accept(new RootContentChildren.CreateSleuthkitNodeVisitor()));
|
||||
DataContentTopComponent.getDefault().setNode(content.accept(new CreateSleuthkitNodeVisitor()));
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,10 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -33,13 +35,11 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.apache.poi.hwpf.usermodel.Picture;
|
||||
import org.apache.poi.hslf.usermodel.HSLFPictureData;
|
||||
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
|
||||
import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.hwpf.HWPFDocument;
|
||||
import org.apache.poi.hwpf.model.PicturesTable;
|
||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
import org.apache.tika.config.TikaConfig;
|
||||
import org.apache.tika.detect.Detector;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
@ -72,13 +72,13 @@ import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Extracts embedded content (e.g. images, audio, video) from Microsoft Office
|
||||
* documents (both original and OOXML forms).
|
||||
* documents (both original and OOXML forms) and PDF documents.
|
||||
*/
|
||||
class MSOfficeEmbeddedContentExtractor {
|
||||
class DocumentEmbeddedContentExtractor {
|
||||
|
||||
private final FileManager fileManager;
|
||||
private final IngestServices services;
|
||||
private static final Logger LOGGER = Logger.getLogger(MSOfficeEmbeddedContentExtractor.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(DocumentEmbeddedContentExtractor.class.getName());
|
||||
private final IngestJobContext context;
|
||||
private String parentFileName;
|
||||
private final String UNKNOWN_IMAGE_NAME_PREFIX = "image_"; //NON-NLS
|
||||
@ -101,7 +101,8 @@ class MSOfficeEmbeddedContentExtractor {
|
||||
PPT("application/vnd.ms-powerpoint"), //NON-NLS
|
||||
PPTX("application/vnd.openxmlformats-officedocument.presentationml.presentation"), //NON-NLS
|
||||
XLS("application/vnd.ms-excel"), //NON-NLS
|
||||
XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); //NON-NLS
|
||||
XLSX("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), //NON-NLS
|
||||
PDF("application/pdf"); //NON-NLS
|
||||
|
||||
private final String mimeType;
|
||||
|
||||
@ -116,7 +117,7 @@ class MSOfficeEmbeddedContentExtractor {
|
||||
}
|
||||
private SupportedExtractionFormats abstractFileExtractionFormat;
|
||||
|
||||
MSOfficeEmbeddedContentExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws NoCurrentCaseException {
|
||||
DocumentEmbeddedContentExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws NoCurrentCaseException {
|
||||
|
||||
this.fileManager = Case.getCurrentCaseThrows().getServices().getFileManager();
|
||||
this.services = IngestServices.getInstance();
|
||||
@ -190,6 +191,9 @@ class MSOfficeEmbeddedContentExtractor {
|
||||
case XLS:
|
||||
listOfExtractedImages = extractImagesFromXls(abstractFile);
|
||||
break;
|
||||
case PDF:
|
||||
listOfExtractedImages = extractEmbeddedContentFromPDF(abstractFile);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -471,6 +475,38 @@ class MSOfficeEmbeddedContentExtractor {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts embedded attachments from PDF files.
|
||||
*
|
||||
* @param abstractFile Input PDF file
|
||||
* @return List of extracted files to be made into derived file instances.
|
||||
*/
|
||||
private List<ExtractedFile> extractEmbeddedContentFromPDF(AbstractFile abstractFile) {
|
||||
PDFAttachmentExtractor pdfExtractor = new PDFAttachmentExtractor(parser);
|
||||
try {
|
||||
Path outputDirectory = Paths.get(getOutputFolderPath(parentFileName));
|
||||
//Get map of attachment name -> location disk.
|
||||
Map<String, Path> extractedAttachments = pdfExtractor.extract(
|
||||
new ReadContentInputStream(abstractFile), abstractFile.getId(),
|
||||
outputDirectory);
|
||||
|
||||
//Convert output to hook into the existing logic for creating derived files
|
||||
List<ExtractedFile> extractedFiles = new ArrayList<>();
|
||||
extractedAttachments.entrySet().forEach((pathEntry) -> {
|
||||
String fileName = pathEntry.getKey();
|
||||
Path writeLocation = pathEntry.getValue();
|
||||
extractedFiles.add(new ExtractedFile(fileName,
|
||||
getFileRelativePath(writeLocation.getFileName().toString()),
|
||||
writeLocation.toFile().length()));
|
||||
});
|
||||
|
||||
return extractedFiles;
|
||||
} catch (IOException | SAXException | TikaException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error attempting to extract attachments from PDFs", ex); //NON-NLS
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes image to the module output location.
|
||||
*
|
@ -50,7 +50,7 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
|
||||
//Outer concurrent hashmap with keys of JobID, inner concurrentHashmap with keys of objectID
|
||||
private static final ConcurrentHashMap<Long, ConcurrentHashMap<Long, Archive>> mapOfDepthTrees = new ConcurrentHashMap<>();
|
||||
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
|
||||
private MSOfficeEmbeddedContentExtractor officeExtractor;
|
||||
private DocumentEmbeddedContentExtractor documentExtractor;
|
||||
private SevenZipExtractor archiveExtractor;
|
||||
private FileTypeDetector fileTypeDetector;
|
||||
private long jobId;
|
||||
@ -115,10 +115,10 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
|
||||
}
|
||||
/*
|
||||
* Construct an embedded content extractor for processing Microsoft
|
||||
* Office documents.
|
||||
* Office documents and PDF documents.
|
||||
*/
|
||||
try {
|
||||
this.officeExtractor = new MSOfficeEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
|
||||
this.documentExtractor = new DocumentEmbeddedContentExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
throw new IngestModuleException(Bundle.EmbeddedFileExtractorIngestModule_UnableToGetMSOfficeExtractor_errMsg(), ex);
|
||||
}
|
||||
@ -155,8 +155,8 @@ public final class EmbeddedFileExtractorIngestModule extends FileIngestModuleAda
|
||||
*/
|
||||
if (archiveExtractor.isSevenZipExtractionSupported(abstractFile)) {
|
||||
archiveExtractor.unpack(abstractFile, mapOfDepthTrees.get(jobId));
|
||||
} else if (officeExtractor.isContentExtractionSupported(abstractFile)) {
|
||||
officeExtractor.extractEmbeddedContent(abstractFile);
|
||||
} else if (documentExtractor.isContentExtractionSupported(abstractFile)) {
|
||||
documentExtractor.extractEmbeddedContent(abstractFile);
|
||||
}
|
||||
return ProcessResult.OK;
|
||||
}
|
||||
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2019 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> 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.modules.embeddedfileextractor;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.parser.AutoDetectParser;
|
||||
import org.apache.tika.parser.ParseContext;
|
||||
import org.apache.tika.parser.Parser;
|
||||
import org.apache.tika.sax.BodyContentHandler;
|
||||
import org.xml.sax.ContentHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.EncodedFileOutputStream;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Facility for extracting and storing attachments from PDF documents.
|
||||
* Implementation specifics, however, are generic enough to be used on any
|
||||
* document with embedded resources. The current name reflects the only known
|
||||
* use case for this class.
|
||||
*/
|
||||
final class PDFAttachmentExtractor {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PDFAttachmentExtractor.class.getName());
|
||||
private final AutoDetectParser parser;
|
||||
|
||||
public PDFAttachmentExtractor() {
|
||||
parser = new AutoDetectParser();
|
||||
}
|
||||
|
||||
public PDFAttachmentExtractor(AutoDetectParser parser) {
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts PDF attachments from a given input and writes them to the supplied
|
||||
* output directory.
|
||||
*
|
||||
* @param input Input PDF to extract attachments from
|
||||
* @param parentID ID for unique extraction names
|
||||
* @param outputDir Directory to write attachments
|
||||
* @return Map containing file name -> location on disk
|
||||
* @throws IOException
|
||||
* @throws SAXException
|
||||
* @throws TikaException
|
||||
*/
|
||||
public Map<String, Path> extract(InputStream input, long parentID, Path outputDir) throws IOException, SAXException, TikaException {
|
||||
ExtractionPreconditions.checkArgument(Files.exists(outputDir),
|
||||
String.format("Output directory: %s, does not exist.", outputDir.toString())); //NON-NLS
|
||||
|
||||
ParseContext parseContext = new ParseContext();
|
||||
parseContext.set(Parser.class, parser);
|
||||
|
||||
//Keep track of the attachment files as they are being extracted and written to disk.
|
||||
NewResourceWatcher watcher = new NewResourceWatcher();
|
||||
parseContext.set(EmbeddedDocumentExtractor.class, new EmbeddedAttachmentHandler(outputDir, parentID, watcher));
|
||||
|
||||
//Parse input with default params, except for our ParseContext
|
||||
parser.parse(input, new BodyContentHandler(-1), new Metadata(), parseContext);
|
||||
|
||||
return watcher.getSnapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal Tika class that is invoked upon encountering an embedded
|
||||
* resource.
|
||||
*/
|
||||
static class EmbeddedAttachmentHandler implements EmbeddedDocumentExtractor {
|
||||
|
||||
private final Path outputDirectory;
|
||||
private final NewResourceWatcher watcher;
|
||||
private final Long parentID;
|
||||
private Integer attachmentCount;
|
||||
|
||||
public EmbeddedAttachmentHandler(Path outputDirectory, long parentID, NewResourceWatcher watcher) {
|
||||
this.outputDirectory = outputDirectory;
|
||||
this.watcher = watcher;
|
||||
this.parentID = parentID;
|
||||
attachmentCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldParseEmbedded(Metadata mtdt) {
|
||||
//Grab every available attachment
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseEmbedded(InputStream in, ContentHandler ch, Metadata mtdt, boolean bln) throws SAXException, IOException {
|
||||
//Resource naming scheme is used internally in autopsy, therefore we can guarentee uniqueness.
|
||||
String uniqueExtractedName = parentID + "_attch_" + attachmentCount++; //NON-NLS
|
||||
|
||||
String name = mtdt.get(Metadata.RESOURCE_NAME_KEY);
|
||||
String ext = FilenameUtils.getExtension(name);
|
||||
|
||||
//Append the extension if we can.
|
||||
if(ext == null) {
|
||||
name = uniqueExtractedName;
|
||||
} else if(!ext.isEmpty()) {
|
||||
uniqueExtractedName += "." + ext;
|
||||
}
|
||||
|
||||
Path outputFile = outputDirectory.resolve(uniqueExtractedName);
|
||||
|
||||
try (EncodedFileOutputStream outputStream = new EncodedFileOutputStream(
|
||||
new FileOutputStream(outputFile.toFile()), TskData.EncodingType.XOR1)){
|
||||
IOUtils.copy(in, outputStream);
|
||||
watcher.notify(name, outputFile);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, String.format("Could not extract attachment %s into directory %s", //NON-NLS
|
||||
uniqueExtractedName, outputFile), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient wrapper for keeping track of new resource paths and the display
|
||||
* name for each of these resources.
|
||||
*
|
||||
* It is necessary to maintain a snapshot of only our changes when the
|
||||
* output directory is shared among other processes/threads.
|
||||
*/
|
||||
static class NewResourceWatcher {
|
||||
|
||||
private final Map<String, Path> newResourcePaths;
|
||||
|
||||
public NewResourceWatcher() {
|
||||
newResourcePaths = new HashMap<>();
|
||||
}
|
||||
|
||||
public void notify(String name, Path newResource) {
|
||||
newResourcePaths.put(name, newResource);
|
||||
}
|
||||
|
||||
public Map<String, Path> getSnapshot() {
|
||||
return newResourcePaths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static convenience methods that ensure the PDF extractor is being invoked
|
||||
* correctly.
|
||||
*/
|
||||
static class ExtractionPreconditions {
|
||||
|
||||
public static void checkArgument(boolean expression, String msg) throws IOException {
|
||||
if (!expression) {
|
||||
throw new IOException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionPreconditions(){
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ public class FileTypeDetector {
|
||||
* @return A list of all detectable file types.
|
||||
*
|
||||
* @throws FileTypeDetectorInitException If an error occurs while assembling
|
||||
* the list of types
|
||||
* the list of types
|
||||
*/
|
||||
public static synchronized SortedSet<String> getDetectedTypes() throws FileTypeDetectorInitException {
|
||||
TreeSet<String> detectedTypes = new TreeSet<>((String string1, String string2) -> {
|
||||
@ -109,7 +109,9 @@ public class FileTypeDetector {
|
||||
* Tika, and Autopsy file type definitions take precendence over Tika.
|
||||
*
|
||||
* @throws FileTypeDetectorInitException If an initialization error occurs,
|
||||
* e.g., user-defined file type definitions exist but cannot be loaded.
|
||||
* e.g., user-defined file type
|
||||
* definitions exist but cannot be
|
||||
* loaded.
|
||||
*/
|
||||
public FileTypeDetector() throws FileTypeDetectorInitException {
|
||||
try {
|
||||
@ -139,7 +141,7 @@ public class FileTypeDetector {
|
||||
* user-defined MIME type by this detector.
|
||||
*
|
||||
* @param customTypes
|
||||
* @param mimeType The MIME type name (e.g., "text/html").
|
||||
* @param mimeType The MIME type name (e.g., "text/html").
|
||||
*
|
||||
* @return True or false.
|
||||
*/
|
||||
@ -170,7 +172,7 @@ public class FileTypeDetector {
|
||||
* @param file The file to test.
|
||||
*
|
||||
* @return A MIME type name. If file type could not be detected, or results
|
||||
* were uncertain, octet-stream is returned.
|
||||
* were uncertain, octet-stream is returned.
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ -223,7 +225,7 @@ public class FileTypeDetector {
|
||||
ReadContentInputStream stream = new ReadContentInputStream(file);
|
||||
|
||||
try (TikaInputStream tikaInputStream = TikaInputStream.get(stream)) {
|
||||
String tikaType = tika.detect(tikaInputStream, file.getName());
|
||||
String tikaType = tika.detect(tikaInputStream);
|
||||
|
||||
/*
|
||||
* Remove the Tika suffix from the MIME type name.
|
||||
@ -234,6 +236,21 @@ public class FileTypeDetector {
|
||||
*/
|
||||
mimeType = removeOptionalParameter(mimeType);
|
||||
|
||||
/*
|
||||
* If Tika recognizes the file signature, then use the file
|
||||
* name to refine the type. In short, this is to exclude the
|
||||
* mime types that are determined solely by file extension.
|
||||
* More details in JIRA-4871.
|
||||
*/
|
||||
if (!mimeType.equals(MimeTypes.OCTET_STREAM)) {
|
||||
ReadContentInputStream secondPassStream = new ReadContentInputStream(file);
|
||||
try (TikaInputStream secondPassTikaStream = TikaInputStream.get(secondPassStream)) {
|
||||
tikaType = tika.detect(secondPassTikaStream, file.getName());
|
||||
mimeType = tikaType.replace("tika-", ""); //NON-NLS
|
||||
mimeType = removeOptionalParameter(mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We cannot trust Tika's audio/mpeg mimetype. Lets verify the
|
||||
* first two bytes and confirm it is not 0xffff. Details in
|
||||
@ -275,6 +292,7 @@ public class FileTypeDetector {
|
||||
* first 4 bits.
|
||||
*
|
||||
* @param x byte
|
||||
*
|
||||
* @return Flag indicating the byte if 0xFF
|
||||
*/
|
||||
private boolean byteIs0xFF(byte x) {
|
||||
@ -284,9 +302,10 @@ public class FileTypeDetector {
|
||||
/**
|
||||
* Retrieves the first N bytes from a file.
|
||||
*
|
||||
* @param file Abstract file to read
|
||||
* @param file Abstract file to read
|
||||
* @param offset Offset to begin reading
|
||||
* @param n Number of bytes to read
|
||||
* @param n Number of bytes to read
|
||||
*
|
||||
* @return Byte array of size n
|
||||
*
|
||||
* @throws TskCoreException
|
||||
@ -371,7 +390,7 @@ public class FileTypeDetector {
|
||||
* Constructs an exception to throw if an initialization error occurs,
|
||||
* e.g., user-defined file type definitions exist but cannot be loaded.
|
||||
*
|
||||
* @param message The exception message,
|
||||
* @param message The exception message,
|
||||
* @param throwable The underlying cause of the exception.
|
||||
*/
|
||||
FileTypeDetectorInitException(String message, Throwable throwable) {
|
||||
@ -409,7 +428,7 @@ public class FileTypeDetector {
|
||||
* @return A MIME type name.
|
||||
*
|
||||
* @throws TskCoreException if detection is required and there is a problem
|
||||
* writing the result to the case database.
|
||||
* writing the result to the case database.
|
||||
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* and AbstractFile.save to save the result to the file object and the
|
||||
* database.
|
||||
@ -429,10 +448,10 @@ public class FileTypeDetector {
|
||||
* @param file The file.
|
||||
*
|
||||
* @return A MIME type name. If file type could not be detected or results
|
||||
* were uncertain, octet-stream is returned.
|
||||
* were uncertain, octet-stream is returned.
|
||||
*
|
||||
* @throws TskCoreException if detection is required and there is a problem
|
||||
* writing the result to the case database.
|
||||
* writing the result to the case database.
|
||||
*
|
||||
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* and AbstractFile.save to save the result to the file object and the
|
||||
@ -453,7 +472,7 @@ public class FileTypeDetector {
|
||||
* @param file The file to test.
|
||||
*
|
||||
* @return A MIME type name. If file type could not be detected or results
|
||||
* were uncertain, octet-stream is returned.
|
||||
* were uncertain, octet-stream is returned.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @deprecated Use getMIMEType instead.
|
||||
|
@ -198,6 +198,7 @@ final class HtmlTextExtractor implements TextExtractor {
|
||||
renderer.setIncludeHyperlinkURLs(false);
|
||||
renderer.setDecorateFontStyles(false);
|
||||
renderer.setIncludeAlternateText(false);
|
||||
renderer.setMaxLineLength(0); // don't force wrapping
|
||||
return new StringReader(renderer.toString());
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error extracting HTML from content.", ex);
|
||||
|
@ -46,6 +46,7 @@ import org.apache.tika.Tika;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.apache.tika.metadata.Metadata;
|
||||
import org.apache.tika.parser.AutoDetectParser;
|
||||
import org.apache.tika.parser.EmptyParser;
|
||||
import org.apache.tika.parser.ParseContext;
|
||||
import org.apache.tika.parser.Parser;
|
||||
import org.apache.tika.parser.ParsingReader;
|
||||
@ -125,6 +126,16 @@ final class TikaTextExtractor implements TextExtractor {
|
||||
"application/x-z", //NON-NLS
|
||||
"application/x-compress"); //NON-NLS
|
||||
|
||||
//Tika should ignore types with embedded files that can be handled by the unpacking modules
|
||||
private static final List<String> EMBEDDED_FILE_MIME_TYPES
|
||||
= ImmutableList.of("application/msword", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", //NON-NLS
|
||||
"application/vnd.ms-powerpoint", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation", //NON-NLS
|
||||
"application/vnd.ms-excel", //NON-NLS
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", //NON-NLS
|
||||
"application/pdf"); //NON-NLS
|
||||
|
||||
private static final java.util.logging.Logger TIKA_LOGGER = java.util.logging.Logger.getLogger("Tika"); //NON-NLS
|
||||
private static final Logger AUTOPSY_LOGGER = Logger.getLogger(TikaTextExtractor.class.getName());
|
||||
|
||||
@ -184,7 +195,14 @@ final class TikaTextExtractor implements TextExtractor {
|
||||
InputStream stream = null;
|
||||
|
||||
ParseContext parseContext = new ParseContext();
|
||||
parseContext.set(Parser.class, parser);
|
||||
|
||||
//Disable appending embedded file text to output for EFE supported types
|
||||
//JIRA-4975
|
||||
if(content instanceof AbstractFile && EMBEDDED_FILE_MIME_TYPES.contains(((AbstractFile)content).getMIMEType())) {
|
||||
parseContext.set(Parser.class, new EmptyParser());
|
||||
} else {
|
||||
parseContext.set(Parser.class, parser);
|
||||
}
|
||||
|
||||
if (ocrEnabled() && content instanceof AbstractFile) {
|
||||
AbstractFile file = ((AbstractFile) content);
|
||||
@ -516,11 +534,11 @@ final class TikaTextExtractor implements TextExtractor {
|
||||
if (context != null) {
|
||||
ImageConfig configInstance = context.lookup(ImageConfig.class);
|
||||
if (configInstance != null) {
|
||||
if(Objects.nonNull(configInstance.getOCREnabled())) {
|
||||
if (Objects.nonNull(configInstance.getOCREnabled())) {
|
||||
this.tesseractOCREnabled = configInstance.getOCREnabled();
|
||||
}
|
||||
|
||||
if(Objects.nonNull(configInstance.getOCRLanguages())) {
|
||||
if (Objects.nonNull(configInstance.getOCRLanguages())) {
|
||||
this.languagePacks = formatLanguagePacks(configInstance.getOCRLanguages());
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
|
||||
import org.sleuthkit.autopsy.texttranslation.TranslationException;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions;
|
||||
|
||||
@ -61,6 +63,8 @@ import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayD
|
||||
@ServiceProvider(service = TextViewer.class, position = 4)
|
||||
public final class TranslatedTextViewer implements TextViewer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName());
|
||||
|
||||
private static final boolean OCR_ENABLED = true;
|
||||
private static final boolean OCR_DISABLED = false;
|
||||
private static final int MAX_SIZE_1MB = 1024000;
|
||||
@ -169,16 +173,20 @@ public final class TranslatedTextViewer implements TextViewer {
|
||||
try {
|
||||
return getFileText(node);
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error getting text", ex);
|
||||
return Bundle.TranslatedContentViewer_errorMsg();
|
||||
} catch (TextExtractor.InitReaderException ex) {
|
||||
logger.log(Level.WARNING, "Error getting text", ex);
|
||||
return Bundle.TranslatedContentViewer_errorExtractingText();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return translate(getFileText(node));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error translating text", ex);
|
||||
return Bundle.TranslatedContentViewer_errorMsg();
|
||||
} catch (TextExtractor.InitReaderException ex) {
|
||||
logger.log(Level.WARNING, "Error translating text", ex);
|
||||
return Bundle.TranslatedContentViewer_errorExtractingText();
|
||||
}
|
||||
}
|
||||
@ -247,7 +255,8 @@ public final class TranslatedTextViewer implements TextViewer {
|
||||
} catch (NoServiceProviderException ex) {
|
||||
return Bundle.TranslatedContentViewer_noServiceProvider();
|
||||
} catch (TranslationException ex) {
|
||||
return Bundle.TranslatedContentViewer_translationException();
|
||||
logger.log(Level.WARNING, "Error translating text", ex);
|
||||
return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +296,7 @@ public final class TranslatedTextViewer implements TextViewer {
|
||||
|
||||
//Correct for UTF-8
|
||||
byte[] resultInUTF8Bytes = result.getBytes("UTF8");
|
||||
byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB / 1000);
|
||||
byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB );
|
||||
return new String(trimTo1MB, "UTF-8");
|
||||
}
|
||||
|
||||
@ -333,7 +342,8 @@ public final class TranslatedTextViewer implements TextViewer {
|
||||
bytesRead += read;
|
||||
}
|
||||
|
||||
return textBuilder.toString();
|
||||
// The trim is on here because HTML files were observed with nearly 1MB of white space at the end
|
||||
return textBuilder.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user