Merge pull request #4874 from kellykelly3/message-threads

Merging develop into message-threads
This commit is contained in:
Richard Cordovano 2019-06-11 15:31:30 -04:00 committed by GitHub
commit 12d2c3872d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
117 changed files with 4864 additions and 858 deletions

View File

@ -45,6 +45,9 @@
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
<dependency conf="core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.3"/>
<dependency org="com.squareup.okhttp" name="okhttp" rev="2.7.5"/>
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
<dependency org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>
</dependencies>
</ivy-module>

View File

@ -111,6 +111,10 @@ file.reference.grpc-context-1.19.0.jar=release/modules/ext/grpc-context-1.19.0.j
file.reference.opencensus-api-0.19.2.jar=release/modules/ext/opencensus-api-0.19.2.jar
file.reference.opencensus-contrib-http-util-0.19.2.jar=release/modules/ext/opencensus-contrib-http-util-0.19.2.jar
file.reference.threetenbp-1.3.3.jar=release/modules/ext/threetenbp-1.3.3.jar
file.reference.okhttp-2.7.5-javadoc.jar=release/modules/ext/okhttp-2.7.5-javadoc.jar
file.reference.okhttp-2.7.5-sources.jar=release/modules/ext/okhttp-2.7.5-sources.jar
file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar
file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt

View File

@ -773,6 +773,14 @@
<runtime-relative-path>ext/google-api-services-translate-v2-rev20170525-1.27.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/google-api-services-translate-v2-rev20170525-1.27.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/okhttp-2.7.5.jar</runtime-relative-path>
<binary-origin>release/modules/ext/okhttp-2.7.5.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/okio-1.6.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/okio-1.6.0.jar</binary-origin>
</class-path-extension>
</data>
</configuration>
</project>

View File

@ -186,13 +186,16 @@ public class LogicalImagerDSProcessor implements DataSourceProcessor {
* associated with the data source that is
* intended to be unique across multiple cases
* (e.g., a UUID).
* @param imageFilePath Path to the image file.
* @param imagePath Path to the image file.
* @param sectorSize The sector size (use '0' for autodetect).
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param chunkSize The maximum size of each chunk of the raw
* data source as it is divided up into virtual
* unallocated space files.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
* @param md5 The MD5 hash of the image, may be null.
* @param sha1 The SHA-1 hash of the image, may be null.
* @param sha256 The SHA-256 hash of the image, may be null.
* @param src The source directory of image.
* @param dest The destination directory to copy the source.
* @param progressMonitor Progress monitor for reporting progress

View File

@ -66,6 +66,7 @@ DataSourceSummaryDialog.window.title=Data Sources Summary
DataSourceSummaryNode.column.dataSourceName.header=Data Source Name
DataSourceSummaryNode.column.files.header=Files
DataSourceSummaryNode.column.results.header=Results
DataSourceSummaryNode.column.status.header=Ingest Status
DataSourceSummaryNode.column.tags.header=Tags
DataSourceSummaryNode.column.type.header=Type
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source

View File

@ -37,8 +37,10 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode.DataSourceSummaryEntryNode;
import static javax.swing.SwingConstants.RIGHT;
import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@ -50,9 +52,10 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataSourceBrowser.class.getName());
private static final int COUNT_COLUMN_WIDTH = 25;
private static final int USAGE_COLUMN_WIDTH = 120;
private static final int DATA_SOURCE_COLUMN_WIDTH = 325;
private static final int COUNT_COLUMN_WIDTH = 20;
private static final int INGEST_STATUS_WIDTH = 50;
private static final int USAGE_COLUMN_WIDTH = 110;
private static final int DATA_SOURCE_COLUMN_WIDTH = 280;
private final Outline outline;
private final org.openide.explorer.view.OutlineView outlineView;
private final ExplorerManager explorerManager;
@ -69,6 +72,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
outlineView = new org.openide.explorer.view.OutlineView();
this.setVisible(true);
outlineView.setPropertyColumns(
Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(),
Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),
Bundle.DataSourceSummaryNode_column_results_header(), Bundle.DataSourceSummaryNode_column_results_header(),
@ -90,6 +94,8 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
column.setPreferredWidth(COUNT_COLUMN_WIDTH);
} else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_type_header())) {
column.setPreferredWidth(USAGE_COLUMN_WIDTH);
} else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_status_header())) {
column.setPreferredWidth(INGEST_STATUS_WIDTH);
} else {
column.setPreferredWidth(DATA_SOURCE_COLUMN_WIDTH);
}
@ -182,6 +188,47 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
return null;
}
/**
* Update the DataSourceBrowser to display up to date status information for
* the data sources.
*
* @param dataSourceId the ID of the data source which should be updated
* @param newStatus the new status which the data source should have
*/
void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) {
//attempt to update the status of any datasources that had status which was STARTED
for (DataSourceSummary summary : dataSourceSummaryList) {
if (summary.getDataSource().getId() == dataSourceId) {
summary.setIngestStatus(newStatus);
}
}
//figure out which nodes were previously selected
Node[] selectedNodes = explorerManager.getSelectedNodes();
SwingUtilities.invokeLater(() -> {
explorerManager.setRootContext(new DataSourceSummaryNode(dataSourceSummaryList));
List<Node> nodesToSelect = new ArrayList<>();
for (Node node : explorerManager.getRootContext().getChildren().getNodes()) {
if (node instanceof DataSourceSummaryEntryNode) {
//there should only be one selected node as multi-select is disabled
for (Node selectedNode : selectedNodes) {
if (((DataSourceSummaryEntryNode) node).getDataSource().equals(((DataSourceSummaryEntryNode) selectedNode).getDataSource())) {
nodesToSelect.add(node);
}
}
}
}
//reselect the previously selected Nodes
try {
explorerManager.setSelectedNodes(nodesToSelect.toArray(new Node[nodesToSelect.size()]));
} catch (PropertyVetoException ex) {
logger.log(Level.WARNING, "Error selecting previously selected nodes", ex);
}
});
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always

View File

@ -18,15 +18,27 @@
*/
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Wrapper object for a DataSource and the information associated with it.
*
*/
class DataSourceSummary {
private static final Logger logger = Logger.getLogger(DataSourceSummary.class.getName());
private static final String INGEST_JOB_STATUS_QUERY = "status_id FROM ingest_jobs WHERE obj_id=";
private final DataSource dataSource;
private IngestJobStatusType status = null;
private final String type;
private final long filesCount;
private final long resultsCount;
@ -45,12 +57,26 @@ class DataSourceSummary {
*/
DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) {
dataSource = dSource;
getStatusFromDatabase();
type = typeValue == null ? "" : typeValue;
filesCount = numberOfFiles == null ? 0 : numberOfFiles;
resultsCount = numberOfResults == null ? 0 : numberOfResults;
tagsCount = numberOfTags == null ? 0 : numberOfTags;
}
/**
* Get the status of the ingest job from the case database
*/
private void getStatusFromDatabase() {
try {
IngestJobQueryCallback callback = new IngestJobQueryCallback();
Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(INGEST_JOB_STATUS_QUERY + dataSource.getId(), callback);
status = callback.getStatus();
} catch (NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.WARNING, "Error getting status for data source from case database", ex);
}
}
/**
* Get the DataSource
*
@ -60,6 +86,16 @@ class DataSourceSummary {
return dataSource;
}
/**
* Manually set the ingest job status
*
* @param ingestStatus the status which the ingest job should have
* currently, null to display empty string
*/
void setIngestStatus(IngestJobStatusType ingestStatus) {
status = ingestStatus;
}
/**
* Get the type of this DataSource
*
@ -87,6 +123,16 @@ class DataSourceSummary {
return resultsCount;
}
/**
* Get the IngestJobStatusType associated with this data source.
*
* @return the IngestJobStatusType associated with this data source. Can be
* null if the IngestJobStatusType is not STARTED or COMPLETED.
*/
IngestJobStatusType getIngestStatus() {
return status;
}
/**
* Get the number of tagged content objects in this DataSource
*
@ -95,4 +141,40 @@ class DataSourceSummary {
long getTagsCount() {
return tagsCount;
}
/**
* Callback to parse result set, getting the status to be associated with
* this data source
*/
class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
private IngestJobStatusType jobStatus = null;
@Override
public void process(ResultSet rs) {
try {
while (rs.next()) {
IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id"));
if (currentStatus == IngestJobStatusType.COMPLETED) {
jobStatus = currentStatus;
} else if (currentStatus == IngestJobStatusType.STARTED) {
jobStatus = currentStatus;
return;
}
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Error getting status for ingest job", ex);
}
}
/**
* Get the status which was determined for this callback
*
* @return the status of the data source which was queried for
*/
IngestJobStatusType getStatus() {
return jobStatus;
}
}
}

View File

@ -19,14 +19,18 @@
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.awt.Frame;
import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Logger;
import javax.swing.event.ListSelectionEvent;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.IngestJobInfo;
/**
* Dialog for displaying the Data Sources Summary information
@ -38,7 +42,6 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
private final DataSourceSummaryDetailsPanel detailsPanel;
private final DataSourceBrowser dataSourcesPanel;
private final IngestJobInfoPanel ingestHistoryPanel;
private static final Logger logger = Logger.getLogger(DataSourceSummaryDialog.class.getName());
/**
* Creates new form DataSourceSummaryDialog for displaying a summary of the
@ -73,6 +76,17 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
this.repaint();
}
});
//add listener to refresh jobs with Started status when they complete
IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
if (evt instanceof DataSourceAnalysisCompletedEvent) {
DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt;
if (dsEvent.getResult() == Reason.ANALYSIS_COMPLETED) {
dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), IngestJobInfo.IngestJobStatusType.COMPLETED);
} else if (dsEvent.getResult() == Reason.ANALYSIS_CANCELLED) {
dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), null);
}
}
});
this.pack();
}

View File

@ -109,6 +109,7 @@ final class DataSourceSummaryNode extends AbstractNode {
static final class DataSourceSummaryEntryNode extends AbstractNode {
private final DataSource dataSource;
private final String status;
private final String type;
private final long filesCount;
private final long resultsCount;
@ -124,6 +125,7 @@ final class DataSourceSummaryNode extends AbstractNode {
DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) {
super(Children.LEAF);
dataSource = dataSourceSummary.getDataSource();
status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().getDisplayName();
type = dataSourceSummary.getType();
filesCount = dataSourceSummary.getFilesCount();
resultsCount = dataSourceSummary.getResultsCount();
@ -143,6 +145,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
@Messages({"DataSourceSummaryNode.column.dataSourceName.header=Data Source Name",
"DataSourceSummaryNode.column.status.header=Ingest Status",
"DataSourceSummaryNode.column.type.header=Type",
"DataSourceSummaryNode.column.files.header=Files",
"DataSourceSummaryNode.column.results.header=Results",
@ -157,6 +160,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(),
dataSource));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), status));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
type));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.casemodule.services;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -30,15 +31,18 @@ import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskData.DbType;
/**
* A per case Autopsy service that manages the addition of content and artifact
@ -48,6 +52,32 @@ public class TagsManager implements Closeable {
private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName());
private final SleuthkitCase caseDb;
static {
//Create the contentviewer tags table (beta) if the current case does not
//have the table present
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), evt -> {
if (evt.getNewValue() != null) {
Case currentCase = (Case) evt.getNewValue();
try {
CaseDbAccessManager caseDb = currentCase.getSleuthkitCase().getCaseDbAccessManager();
if (caseDb.tableExists(ContentViewerTagManager.TABLE_NAME)) {
return;
}
if (currentCase.getSleuthkitCase().getDatabaseType().equals(DbType.SQLITE)) {
caseDb.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE);
} else if (currentCase.getSleuthkitCase().getDatabaseType().equals(DbType.POSTGRESQL)) {
caseDb.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_POSTGRESQL);
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE,
String.format("Unable to create the %s table for image tag storage.",
ContentViewerTagManager.TABLE_NAME), ex);
}
}
});
}
/**
* Tests whether or not a given tag display name contains an illegal

View File

@ -0,0 +1,276 @@
/*
* 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.casemodule.services.contentviewertags;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A per case Autopsy service that manages the addition of content viewer tags
* to the case database. This manager is also responsible for serializing and
* deserializing instances of your tag data objects for persistence and
* retrieval.
*/
public class ContentViewerTagManager {
//Used to convert Java beans into the physical representation that will be stored
//in the database.
private static final ObjectMapper SERIALIZER = new ObjectMapper();
public static final String TABLE_NAME = "beta_tag_app_data";
public static final String TABLE_SCHEMA_SQLITE = "(app_data_id INTEGER PRIMARY KEY, "
+ "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL, "
+ "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))";
public static final String TABLE_SCHEMA_POSTGRESQL = "(app_data_id BIGSERIAL PRIMARY KEY, "
+ "content_tag_id INTEGER NOT NULL, app_data TEXT NOT NULL, "
+ "FOREIGN KEY(content_tag_id) REFERENCES content_tags(tag_id))";
private static final String INSERT_TAG_DATA = "(content_tag_id, app_data) VALUES (%d, '%s')";
private static final String UPDATE_TAG_DATA = "SET content_tag_id = %d, app_data = '%s' WHERE app_data_id = %d";
private static final String SELECT_TAG_DATA = "* FROM " + TABLE_NAME + " WHERE content_tag_id = %d";
private static final String DELETE_TAG_DATA = "WHERE app_data_id = %d";
/**
* Creates and saves a new ContentViewerTag in the case database. The
* generic tag data instance T will be automatically serialized into a
* storable format.
*
* @param <T> Generic class type that will be serialized into a storable
* format for persistence.
* @param contentTag ContentTag that this ContentViewerTag is associated
* with (1:1).
* @param tagDataBean Data instance that contains the tag information to be
* persisted.
* @return An instance of a ContentViewerTag of type T, which contains all
* the stored information.
*
* @throws SerializationException Thrown if the tag data instance T could
* not be serialized into a storable format.
* @throws TskCoreException Thrown if this operation did not successfully
* persist in the case database.
* @throws NoCurrentCaseException Thrown if invocation of this method occurs
* when no case is open.
*/
public static <T> ContentViewerTag<T> saveTag(ContentTag contentTag, T tagDataBean)
throws SerializationException, TskCoreException, NoCurrentCaseException {
try {
long contentTagId = contentTag.getId();
String serialAppData = SERIALIZER.writeValueAsString(tagDataBean);
String insertTemplateInstance = String.format(INSERT_TAG_DATA,
contentTagId, serialAppData);
long insertId = Case.getCurrentCaseThrows()
.getSleuthkitCase()
.getCaseDbAccessManager()
.insert(TABLE_NAME, insertTemplateInstance);
return new ContentViewerTag<>(insertId, contentTag, tagDataBean);
} catch (JsonProcessingException ex) {
throw new SerializationException("Unable to convert object instance into a storable format", ex);
}
}
/**
* Updates the ContentViewerTag instance with the new tag data T and
* persists the changes to the case database.
*
* @param <T> Generic class type that will be serialized into a storable
* format.
* @param oldTag ContentViewerTag instance to be updated
* @param tagDataBean Data instance that contains the updated information to
* be persisted.
*
* @throws SerializationException Thrown if the tag data instance T could
* not be serialized into a storable format.
* @throws TskCoreException Thrown if this operation did not successfully
* persist in the case database.
* @throws NoCurrentCaseException Thrown if invocation of this method occurs
* when no case is open.
*/
public static <T> ContentViewerTag<T> updateTag(ContentViewerTag<T> oldTag, T tagDataBean)
throws SerializationException, TskCoreException, NoCurrentCaseException {
try {
String serialAppData = SERIALIZER.writeValueAsString(tagDataBean);
String updateTemplateInstance = String.format(UPDATE_TAG_DATA,
oldTag.getContentTag().getId(), serialAppData, oldTag.getId());
Case.getCurrentCaseThrows()
.getSleuthkitCase()
.getCaseDbAccessManager()
.update(TABLE_NAME, updateTemplateInstance);
return new ContentViewerTag<>(oldTag.getId(), oldTag.getContentTag(), tagDataBean);
} catch (JsonProcessingException ex) {
throw new SerializationException("Unable to convert object instance into a storable format", ex);
}
}
/**
* Retrieves a ContentViewerTag instance that is associated with the
* specified ContentTag. The Java class T that represents the technical
* details of the tag should be passed so that automatic binding can take
* place.
*
* @param <T> Generic class type that will be instantiated and filled in
* with data.
* @param contentTag ContentTag that this ContentViewerTag is associated
* with (1:1)
* @param clazz Generic class that will be instantiated and filled in with
* data.
* @return ContentViewerTag with an instance of T as a member variable or
* null if the content tag does not have an associated ContentViewerTag of
* type T.
*
* @throws TskCoreException Thrown if this operation did not successfully
* persist in the case database.
* @throws NoCurrentCaseException Thrown if invocation of this method occurs
* when no case is open.
*/
public static <T> ContentViewerTag<T> getTag(ContentTag contentTag, Class<T> clazz) throws TskCoreException, NoCurrentCaseException {
String selectTemplateInstance = String.format(SELECT_TAG_DATA, contentTag.getId());
final ResultWrapper<ContentViewerTag<T>> result = new ResultWrapper<>();
Case.getCurrentCaseThrows()
.getSleuthkitCase()
.getCaseDbAccessManager()
.select(selectTemplateInstance, (ResultSet rs) -> {
try {
if (rs.next()) {
long tagId = rs.getLong(1);
String appDetails = rs.getString(3);
try {
T instance = SERIALIZER.readValue(appDetails, clazz);
result.setResult(new ContentViewerTag<>(tagId, contentTag, instance));
} catch (IOException ex) {
//Databind for type T failed. Not a system error
//but rather a logic error on the part of the caller.
result.setResult(null);
}
}
} catch (SQLException ex) {
result.setException(ex);
}
});
if (result.hasException()) {
throw new TskCoreException("Unable to select tag from case db", result.getException());
}
return result.getResult();
}
/**
* Wrapper for holding state in the CaseDbAccessQueryCallback.
* CaseDbAccessQueryCallback has no support for exception handling.
*
* @param <T>
*/
private static class ResultWrapper<T> {
private T result;
private SQLException ex = null;
public void setResult(T result) {
this.result = result;
}
public void setException(SQLException ex) {
this.ex = ex;
}
public boolean hasException() {
return this.ex != null;
}
public SQLException getException() {
return ex;
}
public T getResult() {
return result;
}
}
/**
* Deletes the content viewer tag with the specified id.
*
* @param contentViewerTag ContentViewerTag to delete
* @throws TskCoreException Thrown if this operation did not successfully
* persist in the case database.
* @throws NoCurrentCaseException Thrown if invocation of this method occurs
* when no case is open.
*/
public static <T> void deleteTag(ContentViewerTag<T> contentViewerTag) throws TskCoreException, NoCurrentCaseException {
String deleteTemplateInstance = String.format(DELETE_TAG_DATA, contentViewerTag.getId());
Case.getCurrentCaseThrows()
.getSleuthkitCase()
.getCaseDbAccessManager()
.delete(TABLE_NAME, deleteTemplateInstance);
}
/**
* This class represents a stored tag in the case database. It is a wrapper
* for the tag id, the attached Content tag object, and the Java bean
* instance that describes the technical details for reconstructing the tag.
*
* @param <T> Generic class type that will be instantiated and filled in
* with data.
*/
public static class ContentViewerTag<T> {
private final long id;
private final ContentTag contentTag;
private final T details;
private ContentViewerTag(long id, ContentTag contentTag, T details) {
this.id = id;
this.contentTag = contentTag;
this.details = details;
}
public long getId() {
return id;
}
public ContentTag getContentTag() {
return contentTag;
}
public T getDetails() {
return details;
}
}
/**
* System exception thrown in the event that class instance T could not be
* properly serialized.
*/
public static class SerializationException extends Exception {
public SerializationException(String message, Exception source) {
super(message, source);
}
}
//Prevent this class from being instantiated.
private ContentViewerTagManager() {
}
}

View File

@ -75,6 +75,10 @@ public final class AccountsBrowser extends JPanel implements ExplorerManager.Pro
public AccountsBrowser() {
initComponents();
jSplitPane1.setResizeWeight(0.5);
jSplitPane1.setDividerLocation(0.75);
outline = outlineView.getOutline();
outlineView.setPropertyColumns(
"device", Bundle.AccountNode_device(),

View File

@ -13,32 +13,7 @@
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="filtersPane" min="-2" pref="265" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="browseVisualizeTabPane" pref="786" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Component id="filtersPane" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Component id="browseVisualizeTabPane" max="32767" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="browseVisualizeTabPane">
<Properties>
@ -51,6 +26,11 @@
<ResourceString bundle="org/sleuthkit/autopsy/communications/Bundle.properties" key="CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="0" insetsBottom="15" insetsRight="15" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
<SubComponents>
@ -90,6 +70,11 @@
<Dimension value="[256, 495]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="15" insetsBottom="15" insetsRight="5" anchor="18" weightX="0.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@ -22,12 +22,13 @@ import com.google.common.eventbus.Subscribe;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JTabbedPane;
import javax.swing.LayoutStyle;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.windows.Mode;
@ -93,39 +94,39 @@ public final class CVTTopComponent extends TopComponent {
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
GridBagConstraints gridBagConstraints;
browseVisualizeTabPane = new JTabbedPane();
accountsBrowser = new AccountsBrowser();
vizPanel = new VisualizationPanel();
filtersPane = new FiltersPanel();
setLayout(new GridBagLayout());
browseVisualizeTabPane.setFont(new Font("Tahoma", 0, 18)); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.accountsBrowser.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/table.png")), accountsBrowser); // NOI18N
browseVisualizeTabPane.addTab(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.vizPanel.TabConstraints.tabTitle_1"), new ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/communications/images/emblem-web.png")), vizPanel); // NOI18N
filtersPane.setMinimumSize(new Dimension(256, 495));
GroupLayout layout = new GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(filtersPane, GroupLayout.PREFERRED_SIZE, 265, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseVisualizeTabPane, GroupLayout.PREFERRED_SIZE, 786, Short.MAX_VALUE)
.addContainerGap())
);
layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(6, 6, 6)
.addComponent(filtersPane, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(5, 5, 5))
.addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(browseVisualizeTabPane)
.addContainerGap())
);
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 1;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new Insets(15, 0, 15, 15);
add(browseVisualizeTabPane, gridBagConstraints);
browseVisualizeTabPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(CVTTopComponent.class, "CVTTopComponent.browseVisualizeTabPane.AccessibleContext.accessibleName")); // NOI18N
filtersPane.setMinimumSize(new Dimension(256, 495));
gridBagConstraints = new GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new Insets(15, 15, 15, 5);
add(filtersPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents

View File

@ -272,9 +272,11 @@ final public class FiltersPanel extends JPanel {
}
});
}
/**
* Populate the devices filter widgets
*
* @param initialState
*/
private void updateDeviceFilter(boolean initialState) {
try {
@ -301,7 +303,7 @@ final public class FiltersPanel extends JPanel {
* Given a list of subFilters, set the states of the panel controls
* accordingly.
*
* @param subFilters A list of subFilters
* @param commFilter Contains a list of subFilters
*/
public void setFilters(CommunicationsFilter commFilter) {
List<CommunicationsFilter.SubFilter> subFilters = commFilter.getAndFilters();

View File

@ -44,3 +44,4 @@ SummaryViewer_CentralRepository_Message=<Enable Central Resposity to see Other O
SummaryViewer_Creation_Date_Title=Creation Date
SummaryViewer_FileRefNameColumn_Title=Path
SummaryViewer_TabTitle=Summary
SummeryViewer_FileRef_Message=<Select one Accout to see File References>

View File

@ -11,29 +11,38 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="contactPane" max="32767" attributes="0"/>
<Component id="outlineViewPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="outlineViewPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="contactPane" pref="332" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane" name="contactPane">
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="outlineViewPanel">
</Component>
<Container class="javax.swing.JSplitPane" name="splitPane">
<Properties>
<Property name="orientation" type="int" value="0"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="23" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane" name="contactPane">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="outlineViewPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -23,7 +23,6 @@ import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import static javax.swing.SwingUtilities.isDescendingFrom;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
@ -126,6 +125,9 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{
updateOutlineViewPanel();
}
});
splitPane.setResizeWeight(0.5);
splitPane.setDividerLocation(0.5);
}
@Override
@ -183,29 +185,32 @@ public final class ContactsViewer extends JPanel implements RelationshipsViewer{
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
splitPane = new javax.swing.JSplitPane();
contactPane = new org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane();
outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(outlineViewPanel, 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(outlineViewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(contactPane, javax.swing.GroupLayout.DEFAULT_SIZE, 332, Short.MAX_VALUE))
);
setLayout(new java.awt.GridBagLayout());
splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
splitPane.setRightComponent(contactPane);
splitPane.setLeftComponent(outlineViewPanel);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(splitPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.communications.relationships.ContactDetailsPane contactPane;
private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel;
private javax.swing.JSplitPane splitPane;
// End of variables declaration//GEN-END:variables
}

View File

@ -11,58 +11,57 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="thumbnailViewer" alignment="1" max="32767" attributes="0"/>
<Component id="contentViewer" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="separator" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="thumbnailViewer" pref="350" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="separator" min="-2" pref="2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="contentViewer" min="-2" pref="450" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail" name="thumbnailViewer">
<Container class="javax.swing.JSplitPane" name="splitPane">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[350, 102]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 400]"/>
</Property>
<Property name="orientation" type="int" value="0"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail(tableEM)"/>
</AuxValues>
</Component>
<Component class="org.sleuthkit.autopsy.contentviewers.MessageContentViewer" name="contentViewer">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 400]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new MessageDataContent()"/>
</AuxValues>
</Component>
<Component class="javax.swing.JSeparator" name="separator">
</Component>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail" name="thumbnailViewer">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[350, 102]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 400]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail(tableEM)"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.contentviewers.MessageContentViewer" name="contentViewer">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 400]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new MessageDataContent()"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -65,6 +65,11 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
* Creates new form ThumbnailViewer
*/
public MediaViewer() {
initComponents();
splitPane.setResizeWeight(0.5);
splitPane.setDividerLocation(0.5);
proxyLookup = new ModifiableProxyLookup(createLookup(tableEM, getActionMap()));
// See org.sleuthkit.autopsy.timeline.TimeLineTopComponent for a detailed
@ -87,8 +92,6 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
}
};
initComponents();
tableEM.addPropertyChangeListener((PropertyChangeEvent evt) -> {
if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) {
handleNodeSelectionChange();
@ -190,43 +193,37 @@ final class MediaViewer extends JPanel implements RelationshipsViewer, ExplorerM
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
splitPane = new javax.swing.JSplitPane();
thumbnailViewer = new org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail(tableEM);
contentViewer = new MessageDataContent();
separator = new javax.swing.JSeparator();
setLayout(new java.awt.GridBagLayout());
splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
thumbnailViewer.setMinimumSize(new java.awt.Dimension(350, 102));
thumbnailViewer.setPreferredSize(new java.awt.Dimension(450, 400));
splitPane.setLeftComponent(thumbnailViewer);
contentViewer.setPreferredSize(new java.awt.Dimension(450, 400));
splitPane.setRightComponent(contentViewer);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(thumbnailViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(contentViewer, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(separator)
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(thumbnailViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(contentViewer, javax.swing.GroupLayout.PREFERRED_SIZE, 450, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(3, 3, 3))
);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(splitPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer;
private javax.swing.JSeparator separator;
private javax.swing.JSplitPane splitPane;
private org.sleuthkit.autopsy.corecomponents.DataResultViewerThumbnail thumbnailViewer;
// End of variables declaration//GEN-END:variables

View File

@ -18,13 +18,10 @@
*/
package org.sleuthkit.autopsy.communications.relationships;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
@ -40,7 +37,6 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TimeUtilities;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.communications.Utils;
@ -72,7 +68,6 @@ final class MessageNode extends BlackboardArtifactNode {
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
List<Tag> tags = getAllTagsFromDatabase();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@ -81,17 +76,6 @@ final class MessageNode extends BlackboardArtifactNode {
sheetSet.put(new NodeProperty<>("Type", Bundle.MessageNode_Node_Property_Type(), "", getDisplayName())); //NON-NLS
addScoreProperty(sheetSet, tags);
CorrelationAttributeInstance correlationAttribute = null;
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
correlationAttribute = getCorrelationAttributeInstance();
}
addCommentProperty(sheetSet, tags, correlationAttribute);
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
addCountProperty(sheetSet, correlationAttribute);
}
final BlackboardArtifact artifact = getArtifact();
BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID());

View File

@ -11,32 +11,41 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,3,13,0,0,2,103"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="contentViewer" max="32767" attributes="0"/>
<Component id="outlineViewPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="outlineViewPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="contentViewer" pref="390" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.contentviewers.MessageContentViewer" name="contentViewer">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new MessageDataContent()"/>
</AuxValues>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="outlineViewPanel">
</Component>
<Container class="javax.swing.JSplitPane" name="splitPane">
<Properties>
<Property name="orientation" type="int" value="0"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.contentviewers.MessageContentViewer" name="contentViewer">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new MessageDataContent()"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="bottom"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="outlineViewPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="left"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -23,7 +23,6 @@ import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import static javax.swing.SwingUtilities.isDescendingFrom;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
@ -68,6 +67,8 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
public MessagesViewer() {
initComponents();
splitPane.setResizeWeight(0.5);
outlineViewPanel.hideOutlineView(Bundle.MessageViewer_no_messages());
proxyLookup = new ModifiableProxyLookup(createLookup(outlineViewPanel.getExplorerManager(), getActionMap()));
@ -138,6 +139,8 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
updateOutlineViewPanel();
}
});
outlineViewPanel.setTableColumnsWidth(5,10,10,15,50,10);
}
@Override
@ -192,29 +195,32 @@ public final class MessagesViewer extends JPanel implements RelationshipsViewer
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
splitPane = new javax.swing.JSplitPane();
contentViewer = new MessageDataContent();
outlineViewPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(outlineViewPanel, 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(outlineViewPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(contentViewer, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE))
);
setLayout(new java.awt.GridBagLayout());
splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
splitPane.setBottomComponent(contentViewer);
splitPane.setLeftComponent(outlineViewPanel);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(splitPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private org.sleuthkit.autopsy.contentviewers.MessageContentViewer contentViewer;
private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel outlineViewPanel;
private javax.swing.JSplitPane splitPane;
// End of variables declaration//GEN-END:variables
}

View File

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -21,6 +24,7 @@
<SubComponents>
<Component class="org.openide.explorer.view.OutlineView" name="outlineView">
<Properties>
<Property name="horizontalScrollBarPolicy" type="int" value="32"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[300, 400]"/>
</Property>

View File

@ -19,7 +19,9 @@
package org.sleuthkit.autopsy.communications.relationships;
import java.awt.CardLayout;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;
import org.openide.explorer.ExplorerManager;
import static org.openide.explorer.ExplorerUtils.createLookup;
import org.openide.explorer.view.OutlineView;
@ -98,7 +100,20 @@ public class OutlineViewPanel extends javax.swing.JPanel implements ExplorerMana
super.setEnabled(enabled);
outlineView.setEnabled(enabled);
}
public void setTableColumnsWidth(double... percentages) {
JTable table = outlineView.getOutline();
double total = 0;
for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
total += percentages[i];
}
for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
column.setPreferredWidth((int) (table.getPreferredSize().width * (percentages[i] / total)));
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -112,8 +127,10 @@ public class OutlineViewPanel extends javax.swing.JPanel implements ExplorerMana
messagePanel = new javax.swing.JPanel();
messageLabel = new javax.swing.JLabel();
setEnabled(false);
setLayout(new java.awt.CardLayout(5, 5));
outlineView.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
outlineView.setPreferredSize(new java.awt.Dimension(300, 400));
add(outlineView, "outlineCard");

View File

@ -11,42 +11,22 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Component id="scrollPane" alignment="1" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Component id="scrollPane" alignment="0" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="scrollPane">
<Properties>
<Property name="verticalScrollBarPolicy" type="int" value="21"/>
</Properties>
<Container class="javax.swing.JTabbedPane" name="tabPane">
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="tabPaneStateChanged"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="1" insetsLeft="1" insetsBottom="1" insetsRight="1" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="javax.swing.JTabbedPane" name="tabPane">
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="tabPaneStateChanged"/>
</Events>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
</Container>
</SubComponents>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
</Container>
</SubComponents>
</Form>

View File

@ -54,14 +54,12 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
tabPane.add(summaryViewer.getDisplayName(), summaryViewer);
tabPane.add(messagesViewer.getDisplayName(), messagesViewer);
tabPane.add(contactsViewer.getDisplayName(), contactsViewer);
tabPane.add(mediaViewer.getDisplayName(), mediaViewer);
tabPane.add(mediaViewer.getDisplayName(), mediaViewer);
}
/**
* Sets the value of currentSelection and passes the SelectionInfo onto the
* currently selected\visible tab.
* currently selected or visible tab.
*
* @param info Currently selected account nodes
*/
@ -78,33 +76,26 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
scrollPane = new javax.swing.JScrollPane();
tabPane = new javax.swing.JTabbedPane();
scrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
setLayout(new java.awt.GridBagLayout());
tabPane.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
tabPaneStateChanged(evt);
}
});
scrollPane.setViewportView(tabPane);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane, javax.swing.GroupLayout.Alignment.TRAILING))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane))
);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(1, 1, 1, 1);
add(tabPane, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents
private void tabPaneStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabPaneStateChanged
@ -121,7 +112,6 @@ public final class RelationshipBrowser extends JPanel implements Lookup.Provider
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane scrollPane;
private javax.swing.JTabbedPane tabPane;
// End of variables declaration//GEN-END:variables

View File

@ -11,36 +11,10 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,3,70,0,0,4,-35"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="countsPanel" max="32767" attributes="0"/>
<Component id="fileReferencesPanel" alignment="0" pref="485" max="32767" attributes="0"/>
<Component id="caseReferencesPanel" alignment="1" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="countsPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fileReferencesPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="caseReferencesPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="countsPanel">
<Properties>
@ -52,6 +26,11 @@
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout>
<DimensionLayout dim="0">
@ -183,7 +162,7 @@
</Component>
</SubComponents>
</Container>
<Component class="org.sleuthkit.autopsy.communications.OutlineViewPanel" name="fileReferencesPanel">
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
@ -192,12 +171,14 @@
</TitledBorder>
</Border>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[472, 300]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.OutlineViewPanel" name="caseReferencesPanel">
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
@ -206,10 +187,12 @@
</TitledBorder>
</Border>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[472, 300]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@ -46,7 +46,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
"SummaryViewer_FileRefNameColumn_Title=Path",
"SummaryViewer_CaseRefNameColumn_Title=Case Name",
"SummaryViewer_CentralRepository_Message=<Enable Central Resposity to see Other Occurrences>",
"SummaryViewer_Creation_Date_Title=Creation Date"
"SummaryViewer_Creation_Date_Title=Creation Date",
"SummeryViewer_FileRef_Message=<Select one Accout to see File References>",
})
/**
@ -70,6 +71,9 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.SummaryViewer_CaseRefNameColumn_Title());
clearControls();
caseReferencesPanel.hideOutlineView(Bundle.SummaryViewer_CentralRepository_Message());
fileReferencesPanel.hideOutlineView(Bundle.SummeryViewer_FileRef_Message());
}
@Override
@ -96,7 +100,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
if (info.getAccounts().size() != 1) {
setEnabled(false);
clearControls();
fileReferencesPanel.hideOutlineView(Bundle.SummeryViewer_FileRef_Message());
} else {
SelectionSummary summaryDetails = info.getSummary();
@ -105,6 +110,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
contactsDataLabel.setText(Integer.toString(summaryDetails.getContactsCnt()));
emailDataLabel.setText(Integer.toString(summaryDetails.getEmailCnt()));
messagesDataLabel.setText(Integer.toString(summaryDetails.getMessagesCnt()));
fileReferencesPanel.showOutlineView();
fileReferencesPanel.setNode(new AbstractNode(Children.create(new AccountSourceContentChildNodeFactory(info.getAccounts()), true)));
caseReferencesPanel.setNode(new AbstractNode(Children.create(new CorrelationCaseChildNodeFactory(info.getAccounts()), true)));
@ -178,6 +185,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
countsPanel = new javax.swing.JPanel();
emailLabel = new javax.swing.JLabel();
@ -193,6 +201,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel();
setLayout(new java.awt.GridBagLayout());
countsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.countsPanel.border.title"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(emailLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.emailLabel.text")); // NOI18N
@ -262,35 +272,33 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
add(countsPanel, gridBagConstraints);
fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N
fileReferencesPanel.setPreferredSize(new java.awt.Dimension(472, 300));
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(fileReferencesPanel, gridBagConstraints);
caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N
caseReferencesPanel.setPreferredSize(new java.awt.Dimension(472, 300));
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(countsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(fileReferencesPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 485, Short.MAX_VALUE)
.addComponent(caseReferencesPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(countsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fileReferencesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(caseReferencesPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
add(caseReferencesPanel, gridBagConstraints);
}// </editor-fold>//GEN-END:initComponents

View File

@ -83,9 +83,10 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Show Images
HtmlPanel.showImagesToggleButton.text=Download Images
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaViewImagePanel.tagsMenu.text_1=Tags Menu

View File

@ -22,7 +22,7 @@ GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
GstVideoPanel.noOpenCase.errMsg=No open case available.
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
HtmlPanel_showImagesToggleButton_hide=Hide Images
HtmlPanel_showImagesToggleButton_show=Show Images
HtmlPanel_showImagesToggleButton_show=Download Images
HtmlViewer_file_error=This file is missing or unreadable.
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
@ -44,7 +44,11 @@ MediaPlayerPanel.timeFormat=%02d:%02d:%02d
MediaPlayerPanel.unknownTime=Unknown
MediaViewImagePanel.errorLabel.OOMText=Could not load file into Media View: insufficent memory.
MediaViewImagePanel.errorLabel.text=Could not load file into Media View.
MediaViewImagePanel.exportSaveText=Save
MediaViewImagePanel.externalViewerButton.text=Open in External Viewer Ctrl+E
MediaViewImagePanel.fileChooserTitle=Choose a save location
MediaViewImagePanel.successfulExport=Tagged image was successfully saved.
MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.
MediaViewVideoPanel.pauseButton.text=\u25ba
MediaViewVideoPanel.progressLabel.text=00:00
MediaViewVideoPanel.infoLabel.text=info
@ -145,12 +149,13 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
HtmlPanel.showImagesToggleButton.text=Show Images
HtmlPanel.showImagesToggleButton.text=Download Images
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
# {0} - tableName
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
# {0} - tableName

View File

@ -77,12 +77,12 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer
}
/**
* Get the FileTypeViewer for a given mimetype
* Get the FileTypeViewer for a given file
*
* @param mimeType
* @param file
*
* @return FileTypeViewer, null if no known content viewer supports the
* mimetype
* file
*/
private FileTypeViewer getSupportingViewer(AbstractFile file) {
FileTypeViewer viewer = mimeTypeToViewerMap.get(file.getMIMEType());

View File

@ -18,7 +18,7 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="showImagesToggleButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="95" max="32767" attributes="0"/>
<EmptySpace min="0" pref="75" max="32767" attributes="0"/>
</Group>
<Component id="htmlJPanel" max="32767" attributes="0"/>
</Group>

View File

@ -18,6 +18,11 @@
*/
package org.sleuthkit.autopsy.contentviewers;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@ -25,8 +30,9 @@ 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.Node;
import net.htmlparser.jericho.Attribute;
import net.htmlparser.jericho.OutputDocument;
import net.htmlparser.jericho.Source;
import org.openide.util.NbBundle.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
@ -38,6 +44,7 @@ import org.w3c.dom.events.EventTarget;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class HtmlPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(HtmlPanel.class.getName());
private static final long serialVersionUID = 1L;
private static final String TEXT_TYPE = "text/plain";
private final JFXPanel jfxPanel = new JFXPanel();
@ -92,26 +99,76 @@ final class HtmlPanel extends javax.swing.JPanel {
}
/**
* Cleans out input HTML string
* Cleans out input HTML string so it will not access resources over the internet
*
* @param htmlInString The HTML string to cleanse
*
* @return The cleansed HTML String
*/
private String cleanseHTML(String htmlInString) {
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();
String returnString = "";
try {
Source source = new Source(new StringReader(htmlInString));
OutputDocument document = new OutputDocument(source);
//remove background images
source.getAllTags().stream().filter((tag) -> (tag.toString().contains("background-image"))).forEachOrdered((tag) -> {
document.remove(tag);
});
//remove images
source.getAllElements("img").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove frames
source.getAllElements("frame").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove iframes
source.getAllElements("iframe").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove pictures
source.getAllElements("picture").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove svg
source.getAllElements("svg").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove audio
source.getAllElements("audio").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove video
source.getAllElements("video").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove tracks
source.getAllElements("track").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove embeded external elements
source.getAllElements("embed").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove linked elements
source.getAllElements("link").forEach((element) -> {
document.remove(element.getAllTags());
});
//remove other URI elements such as input boxes
List<Attribute> attributesToRemove = source.getURIAttributes();
document.remove(attributesToRemove);
returnString = document.toString();
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to read html for cleaning out URI elements with Jericho", ex);
}
return returnString;
}
/**
* Refresh the panel to reflect the current show/hide images setting.
*/
@Messages({
"HtmlPanel_showImagesToggleButton_show=Show Images",
"HtmlPanel_showImagesToggleButton_show=Download Images",
"HtmlPanel_showImagesToggleButton_hide=Hide Images",
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",})
private void refresh() {
@ -164,7 +221,7 @@ final class HtmlPanel extends javax.swing.JPanel {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
.addGap(0, 95, Short.MAX_VALUE))
.addGap(0, 75, Short.MAX_VALUE))
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(

View File

@ -19,7 +19,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,100,0,0,3,117"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBoxLayout">
@ -200,6 +200,44 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="zoomResetButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.Box$Filler" name="filler1">
</Component>
<Component class="javax.swing.Box$Filler" name="filler2">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 0]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.HorizontalGlue"/>
</AuxValues>
</Component>
<Container class="javax.swing.JPanel" name="jPanel1">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
<Component class="javax.swing.JButton" name="tagsMenu">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaViewImagePanel.tagsMenu.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[75, 21]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[75, 21]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[75, 21]"/>
</Property>
<Property name="verticalTextPosition" type="int" value="3"/>
</Properties>
<Events>
<EventHandler event="mousePressed" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="tagsMenuMousePressed"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>

View File

@ -20,18 +20,31 @@ package org.sleuthkit.autopsy.contentviewers;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static java.util.Objects.nonNull;
import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.ListChangeListener.Change;
import javafx.concurrent.Task;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
@ -45,19 +58,39 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javafx.scene.Node;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.apache.commons.io.FilenameUtils;
import org.controlsfx.control.MaskerPane;
import org.openide.util.NbBundle;
import org.python.google.common.collect.Lists;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog.TagNameAndComment;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.SerializationException;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtility;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagControls;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagCreator;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTag;
import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsGroup;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Image viewer part of the Media View layered pane. Uses JavaFX to display the
@ -70,17 +103,30 @@ import org.sleuthkit.datamodel.AbstractFile;
class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPanel {
private static final Image EXTERNAL = new Image(MediaViewImagePanel.class.getResource("/org/sleuthkit/autopsy/images/external.png").toExternalForm());
private final static Logger LOGGER = Logger.getLogger(MediaViewImagePanel.class.getName());
private final boolean fxInited;
private JFXPanel fxPanel;
private Group imageGroup;
private ImageTaggingTool tagger;
private AbstractFile file;
private Group masterGroup;
private ImageTagsGroup tagsGroup;
private ImageTagCreator imageTagCreator;
private ImageView fxImageView;
private ScrollPane scrollPane;
private final ProgressBar progressBar = new ProgressBar();
private final MaskerPane maskerPane = new MaskerPane();
private final JPopupMenu popupMenu = new JPopupMenu();
private final JMenuItem createTagMenuItem;
private final JMenuItem deleteTagMenuItem;
private final JMenuItem hideTagsMenuItem;
private final JMenuItem exportTagsMenuItem;
private final JFileChooser exportChooser;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private double zoomRatio;
private double rotation; // Can be 0, 90, 180, and 270.
@ -116,33 +162,163 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
public MediaViewImagePanel() {
initComponents();
fxInited = org.sleuthkit.autopsy.core.Installer.isJavaFxInited();
exportChooser = new JFileChooser();
exportChooser.setDialogTitle(Bundle.MediaViewImagePanel_fileChooserTitle());
//Build popupMenu when Tags Menu button is pressed.
createTagMenuItem = new JMenuItem("Create");
createTagMenuItem.addActionListener((event) -> createTag());
popupMenu.add(createTagMenuItem);
popupMenu.add(new JSeparator());
deleteTagMenuItem = new JMenuItem("Delete");
deleteTagMenuItem.addActionListener((event) -> deleteTag());
popupMenu.add(deleteTagMenuItem);
popupMenu.add(new JSeparator());
hideTagsMenuItem = new JMenuItem("Hide");
hideTagsMenuItem.addActionListener((event) -> showOrHideTags());
popupMenu.add(hideTagsMenuItem);
popupMenu.add(new JSeparator());
exportTagsMenuItem = new JMenuItem("Export");
exportTagsMenuItem.addActionListener((event) -> exportTags());
popupMenu.add(exportTagsMenuItem);
popupMenu.setPopupSize(300, 150);
if (fxInited) {
Platform.runLater(() -> {
Platform.runLater(new Runnable() {
@Override
public void run() {
// build jfx ui (we could do this in FXML?)
fxImageView = new ImageView(); // will hold image
masterGroup = new Group(fxImageView);
tagsGroup = new ImageTagsGroup(fxImageView);
tagsGroup.getChildren().addListener((Change<? extends Node> c) -> {
if (c.getList().isEmpty()) {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.EMPTY));
}
});
// build jfx ui (we could do this in FXML?)
fxImageView = new ImageView(); // will hold image
imageGroup = new Group();
imageGroup.getChildren().add(fxImageView);
scrollPane = new ScrollPane(imageGroup); // scrolls and sizes imageview
scrollPane.getStyleClass().add("bg"); //NOI18N
scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
subscribeTagMenuItemsToStateChanges();
fxPanel = new JFXPanel(); // bridge jfx-swing
Scene scene = new Scene(scrollPane); //root of jfx tree
scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
fxPanel.setScene(scene);
masterGroup.getChildren().add(tagsGroup);
fxImageView.setSmooth(true);
fxImageView.setCache(true);
//Update buttons when users select (or unselect) image tags.
tagsGroup.addFocusChangeListener((event) -> {
if (event.getPropertyName().equals(ImageTagControls.NOT_FOCUSED.getName())) {
if (masterGroup.getChildren().contains(imageTagCreator)) {
return;
}
EventQueue.invokeLater(() -> {
add(fxPanel);//add jfx ui to JPanel
});
if (tagsGroup.getChildren().isEmpty()) {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.EMPTY));
} else {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.CREATE));
}
} else if (event.getPropertyName().equals(ImageTagControls.FOCUSED.getName())) {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.SELECTED));
}
});
scrollPane = new ScrollPane(masterGroup); // scrolls and sizes imageview
scrollPane.getStyleClass().add("bg"); //NOI18N
scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED);
fxPanel = new JFXPanel(); // bridge jfx-swing
Scene scene = new Scene(scrollPane); //root of jfx tree
scene.getStylesheets().add(MediaViewImagePanel.class.getResource("MediaViewImagePanel.css").toExternalForm()); //NOI18N
fxPanel.setScene(scene);
fxImageView.setSmooth(true);
fxImageView.setCache(true);
EventQueue.invokeLater(() -> {
add(fxPanel);//add jfx ui to JPanel
});
}
});
}
}
/**
* Handle tags menu item enabling and disabling given the state of the
* content viewer. For example, when the tags group is empty (no tags on image),
* disable delete menu item, hide menu item, and export menu item.
*/
private void subscribeTagMenuItemsToStateChanges() {
pcs.addPropertyChangeListener((event) -> {
State currentState = (State) event.getNewValue();
switch (currentState) {
case CREATE:
createTagMenuItem.setEnabled(true);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(true);
exportTagsMenuItem.setEnabled(true);
break;
case SELECTED:
if (masterGroup.getChildren().contains(imageTagCreator)) {
imageTagCreator.disconnect();
masterGroup.getChildren().remove(imageTagCreator);
}
createTagMenuItem.setEnabled(false);
deleteTagMenuItem.setEnabled(true);
hideTagsMenuItem.setEnabled(true);
exportTagsMenuItem.setEnabled(true);
break;
case HIDDEN:
createTagMenuItem.setEnabled(false);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(true);
hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
exportTagsMenuItem.setEnabled(false);
break;
case VISIBLE:
createTagMenuItem.setEnabled(true);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(true);
hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
exportTagsMenuItem.setEnabled(true);
break;
case DEFAULT:
case EMPTY:
if (masterGroup.getChildren().contains(imageTagCreator)) {
imageTagCreator.disconnect();
}
createTagMenuItem.setEnabled(true);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(false);
hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
exportTagsMenuItem.setEnabled(false);
break;
case NONEMPTY:
createTagMenuItem.setEnabled(true);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(true);
exportTagsMenuItem.setEnabled(true);
break;
case DISABLE:
createTagMenuItem.setEnabled(false);
deleteTagMenuItem.setEnabled(false);
hideTagsMenuItem.setEnabled(false);
exportTagsMenuItem.setEnabled(false);
break;
default:
break;
}
});
}
public boolean isInited() {
return fxInited;
}
@ -154,10 +330,11 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
Platform.runLater(() -> {
fxImageView.setViewport(new Rectangle2D(0, 0, 0, 0));
fxImageView.setImage(null);
tagger.defaultSettings();
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.DEFAULT));
masterGroup.getChildren().clear();
scrollPane.setContent(null);
scrollPane.setContent(imageGroup);
scrollPane.setContent(masterGroup);
});
}
@ -205,14 +382,31 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
try {
Image fxImage = readImageTask.get();
masterGroup.getChildren().clear();
tagsGroup.getChildren().clear();
this.file = file;
if (nonNull(fxImage)) {
// We have a non-null image, so let's show it.
fxImageView.setImage(fxImage);
imageGroup.getChildren().remove(tagger);
tagger = new ImageTaggingTool(fxImageView, Color.RED);
imageGroup.getChildren().add(tagger);
resetView();
scrollPane.setContent(imageGroup);
masterGroup.getChildren().add(fxImageView);
masterGroup.getChildren().add(tagsGroup);
try {
List<ContentTag> tags = Case.getCurrentCase().getServices()
.getTagsManager().getContentTagsByContent(file);
List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
//Add all image tags
tagsGroup = buildImageTagsGroup(contentViewerTags);
if (!tagsGroup.getChildren().isEmpty()) {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.NONEMPTY));
}
} catch (TskCoreException | NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Could not retrieve image tags for file in case db", ex); //NON-NLS
}
scrollPane.setContent(masterGroup);
} else {
showErrorNode(Bundle.MediaViewImagePanel_errorLabel_text(), file);
}
@ -252,6 +446,52 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
});
}
/**
* Finds all ContentViewerTags that are of type 'ImageTagRegion' for the
* current file.
*
* @param contentTags
* @return
* @throws TskCoreException
* @throws NoCurrentCaseException
*/
private List<ContentViewerTag<ImageTagRegion>> getContentViewerTags(List<ContentTag> contentTags)
throws TskCoreException, NoCurrentCaseException {
List<ContentViewerTag<ImageTagRegion>> contentViewerTags = new ArrayList<>();
for (ContentTag contentTag : contentTags) {
ContentViewerTag<ImageTagRegion> contentViewerTag = ContentViewerTagManager
.getTag(contentTag, ImageTagRegion.class);
if (contentViewerTag == null) {
continue;
}
contentViewerTags.add(contentViewerTag);
}
return contentViewerTags;
}
/**
* Builds ImageTag instances from stored ContentViewerTags of the
* appropriate type.
*
* @param contentTags
* @return
* @throws TskCoreException
* @throws NoCurrentCaseException
*/
private ImageTagsGroup buildImageTagsGroup(List<ContentViewerTag<ImageTagRegion>> contentViewerTags) {
contentViewerTags.forEach(contentViewerTag -> {
/**
* Build the image tag, add an edit event call back to persist all
* edits made on this image tag instance.
*/
tagsGroup.getChildren().add(buildImageTag(contentViewerTag));
});
return tagsGroup;
}
/**
* @return supported mime types
*/
@ -303,6 +543,10 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
zoomInButton = new javax.swing.JButton();
jSeparator2 = new javax.swing.JToolBar.Separator();
zoomResetButton = new javax.swing.JButton();
filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0));
filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0));
jPanel1 = new javax.swing.JPanel();
tagsMenu = new javax.swing.JButton();
setBackground(new java.awt.Color(0, 0, 0));
addComponentListener(new java.awt.event.ComponentAdapter() {
@ -406,6 +650,23 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
}
});
toolbar.add(zoomResetButton);
toolbar.add(filler1);
toolbar.add(filler2);
toolbar.add(jPanel1);
org.openide.awt.Mnemonics.setLocalizedText(tagsMenu, org.openide.util.NbBundle.getMessage(MediaViewImagePanel.class, "MediaViewImagePanel.tagsMenu.text_1")); // NOI18N
tagsMenu.setFocusable(false);
tagsMenu.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
tagsMenu.setMaximumSize(new java.awt.Dimension(75, 21));
tagsMenu.setMinimumSize(new java.awt.Dimension(75, 21));
tagsMenu.setPreferredSize(new java.awt.Dimension(75, 21));
tagsMenu.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
tagsMenu.addMouseListener(new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
tagsMenuMousePressed(evt);
}
});
toolbar.add(tagsMenu);
add(toolbar);
}// </editor-fold>//GEN-END:initComponents
@ -450,12 +711,228 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
updateView();
}//GEN-LAST:event_formComponentResized
/**
* Deletes the selected tag when the Delete button is pressed in the Tag
* Menu.
*/
private void deleteTag() {
Platform.runLater(() -> {
ImageTag tagInFocus = tagsGroup.getFocus();
if (tagInFocus == null) {
return;
}
try {
ContentViewerTag<ImageTagRegion> contentViewerTag = tagInFocus.getContentViewerTag();
scrollPane.setCursor(Cursor.WAIT);
ContentViewerTagManager.deleteTag(contentViewerTag);
Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(contentViewerTag.getContentTag());
tagsGroup.getChildren().remove(tagInFocus);
} catch (TskCoreException | NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Could not delete image tag in case db", ex); //NON-NLS
}
scrollPane.setCursor(Cursor.DEFAULT);
});
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.CREATE));
}
/**
* Enables create tag logic when the Create button is pressed in the Tags
* Menu.
*/
private void createTag() {
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.DISABLE));
imageTagCreator = new ImageTagCreator(fxImageView);
PropertyChangeListener newTagListener = (event) -> {
SwingUtilities.invokeLater(() -> {
ImageTagRegion tag = (ImageTagRegion) event.getNewValue();
//Ask the user for tag name and comment
TagNameAndComment result = GetTagNameAndCommentDialog.doDialog();
if (result != null) {
//Persist and build image tag
Platform.runLater(() -> {
try {
scrollPane.setCursor(Cursor.WAIT);
ContentViewerTag<ImageTagRegion> contentViewerTag = storeImageTag(tag, result);
ImageTag imageTag = buildImageTag(contentViewerTag);
tagsGroup.getChildren().add(imageTag);
} catch (TskCoreException | SerializationException | NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Could not save new image tag in case db", ex); //NON-NLS
}
scrollPane.setCursor(Cursor.DEFAULT);
});
}
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.CREATE));
});
//Remove image tag creator from panel
Platform.runLater(() -> {
imageTagCreator.disconnect();
masterGroup.getChildren().remove(imageTagCreator);
});
};
imageTagCreator.addNewTagListener(newTagListener);
Platform.runLater(() -> masterGroup.getChildren().add(imageTagCreator));
}
/**
* Creates an ImageTag instance from the ContentViewerTag.
*
* @param contentViewerTag
* @return
*/
private ImageTag buildImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag) {
ImageTag imageTag = new ImageTag(contentViewerTag, fxImageView);
//Automatically persist edits made by user
imageTag.subscribeToEditEvents((edit) -> {
try {
scrollPane.setCursor(Cursor.WAIT);
ImageTagRegion newRegion = (ImageTagRegion) edit.getNewValue();
ContentViewerTagManager.updateTag(contentViewerTag, newRegion);
} catch (SerializationException | TskCoreException | NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Could not save edit for image tag in case db", ex); //NON-NLS
}
scrollPane.setCursor(Cursor.DEFAULT);
});
return imageTag;
}
/**
* Stores the image tag by creating a ContentTag instance and associating
* the ImageTagRegion data with it in the case database.
*
* @param data
* @param result
*/
private ContentViewerTag<ImageTagRegion> storeImageTag(ImageTagRegion data, TagNameAndComment result)
throws TskCoreException, SerializationException, NoCurrentCaseException {
scrollPane.setCursor(Cursor.WAIT);
try {
ContentTag contentTag = Case.getCurrentCaseThrows().getServices().getTagsManager()
.addContentTag(file, result.getTagName(), result.getComment());
return ContentViewerTagManager.saveTag(contentTag, data);
} finally {
scrollPane.setCursor(Cursor.DEFAULT);
}
}
/**
* Hides or show tags when the Hide or Show button is pressed in the Tags
* Menu.
*/
private void showOrHideTags() {
Platform.runLater(() -> {
if (DisplayOptions.HIDE_TAGS.getName().equals(hideTagsMenuItem.getText())) {
//Temporarily remove the tags group and update buttons
masterGroup.getChildren().remove(tagsGroup);
hideTagsMenuItem.setText(DisplayOptions.SHOW_TAGS.getName());
tagsGroup.clearFocus();
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.HIDDEN));
} else {
//Add tags group back in and update buttons
masterGroup.getChildren().add(tagsGroup);
hideTagsMenuItem.setText(DisplayOptions.HIDE_TAGS.getName());
pcs.firePropertyChange(new PropertyChangeEvent(this,
"state", null, State.VISIBLE));
}
});
}
@NbBundle.Messages({
"MediaViewImagePanel.exportSaveText=Save",
"MediaViewImagePanel.successfulExport=Tagged image was successfully saved.",
"MediaViewImagePanel.unsuccessfulExport=Unable to export tagged image to disk.",
"MediaViewImagePanel.fileChooserTitle=Choose a save location"
})
private void exportTags() {
tagsGroup.clearFocus();
exportChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
//Always base chooser location to export folder
exportChooser.setCurrentDirectory(new File(Case.getCurrentCase().getExportDirectory()));
int returnVal = exportChooser.showDialog(this, Bundle.MediaViewImagePanel_exportSaveText());
if (returnVal == JFileChooser.APPROVE_OPTION) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() {
try {
List<ContentTag> tags = Case.getCurrentCase().getServices()
.getTagsManager().getContentTagsByContent(file);
List<ContentViewerTag<ImageTagRegion>> contentViewerTags = getContentViewerTags(tags);
Collection<ImageTagRegion> regions = contentViewerTags.stream()
.map(cvTag -> cvTag.getDetails()).collect(Collectors.toList());
byte[] jpgImage = ImageTagsUtility.exportTags(file, regions, ".jpg");
Path output = Paths.get(exportChooser.getSelectedFile().getPath(),
FilenameUtils.getBaseName(file.getName()) + "-with_tags.jpg"); //NON-NLS
Files.write(output, jpgImage);
JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_successfulExport());
} catch (TskCoreException | NoCurrentCaseException | IOException ex) {
LOGGER.log(Level.WARNING, "Unable to export tagged image to disk", ex); //NON-NLS
JOptionPane.showMessageDialog(null, Bundle.MediaViewImagePanel_unsuccessfulExport());
}
return null;
}
}.execute();
}
}
private void tagsMenuMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tagsMenuMousePressed
popupMenu.show(tagsMenu, -300 + tagsMenu.getWidth(), tagsMenu.getHeight() + 3);
}//GEN-LAST:event_tagsMenuMousePressed
/**
* Display states for the show/hide tags button.
*/
enum DisplayOptions {
HIDE_TAGS("Hide"),
SHOW_TAGS("Show");
private final String name;
DisplayOptions(String name) {
this.name = name;
}
String getName() {
return name;
}
}
/**
* Different states that the content viewer can be in. These states drive
* which buttons are enabled for tagging.
*/
enum State {
HIDDEN,
VISIBLE,
SELECTED,
CREATE,
EMPTY,
NONEMPTY,
DEFAULT,
DISABLE;
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.Box.Filler filler1;
private javax.swing.Box.Filler filler2;
private javax.swing.JPanel jPanel1;
private javax.swing.JToolBar.Separator jSeparator1;
private javax.swing.JToolBar.Separator jSeparator2;
private javax.swing.JButton rotateLeftButton;
private javax.swing.JButton rotateRightButton;
private javax.swing.JTextField rotationTextField;
private javax.swing.JButton tagsMenu;
private javax.swing.JToolBar toolbar;
private javax.swing.JButton zoomInButton;
private javax.swing.JButton zoomOutButton;
@ -601,8 +1078,8 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
// Add the transforms in reverse order of intended execution.
// Note: They MUST be added in this order to ensure translate is
// executed last.
imageGroup.getTransforms().clear();
imageGroup.getTransforms().addAll(translate, rotate, scale);
masterGroup.getTransforms().clear();
masterGroup.getTransforms().addAll(translate, rotate, scale);
// Adjust scroll bar positions for view changes.
if (viewportWidth > fxPanel.getWidth()) {
@ -618,107 +1095,4 @@ class MediaViewImagePanel extends JPanel implements MediaFileViewer.MediaViewPan
rotationTextField.setText((int) rotation + "°");
zoomTextField.setText((Math.round(zoomRatio * 100.0)) + "%");
}
/**
* Enables users to 'tag' a region of an image by clicking and dragging a
* rectangle overtop.
*/
class ImageTaggingTool extends Rectangle {
private final double imageWidth;
private final double imageHeight;
private final double imageOriginX;
private final double imageOriginY;
//Origin of the drag event.
private double rectangleOriginX;
private double rectangleOriginY;
//Rectangle lines should be 1.5% of the image. This level of thickness has
//a good balance between visual acuity and loss of selection at the borders
//of the image.
private double lineThicknessAsPercent = 1.5;
/**
* Adds tagging support to an image, where the 'tag' rectangle will be
* the specified color.
*
* @param image Image to tag
* @param color Color of the 'tag' rectangle
*/
private ImageTaggingTool(ImageView image, Color color) {
defaultSettings();
imageWidth = image.getImage().getWidth();
imageHeight = image.getImage().getHeight();
imageOriginX = image.getX();
imageOriginY = image.getY();
setStroke(color);
setFill(color.deriveColor(0, 0, 0, 0));
//Calculate how many pixels the stroke width should be to guarentee
//a consistent % of image consumed by the rectangle border.
double min = Math.min(imageWidth, imageHeight);
double lineThicknessPixels = min * lineThicknessAsPercent / 100.0;
setStrokeWidth(lineThicknessPixels);
setVisible(false);
//Create a rectangle by left clicking on the image
image.setOnMousePressed((MouseEvent event) -> {
if (event.isSecondaryButtonDown()) {
return;
}
//Reset box on new click.
defaultSettings();
rectangleOriginX = event.getX();
rectangleOriginY = event.getY();
setX(rectangleOriginX);
setY(rectangleOriginY);
});
//Adjust the rectangle by dragging the left mouse button
image.setOnMouseDragged((MouseEvent event) -> {
if (event.isSecondaryButtonDown()) {
return;
}
/**
* Ensure the rectangle is contained within image boundaries and
* that the line thickness is kept within bounds.
*/
double newX = Math.min(Math.max(event.getX(), imageOriginX)
+ lineThicknessPixels / 2, imageWidth - lineThicknessPixels / 2);
double newY = Math.min(Math.max(event.getY(), imageOriginY)
+ lineThicknessPixels / 2, imageHeight - lineThicknessPixels / 2);
setVisible(true);
double offsetX = newX - rectangleOriginX;
if (offsetX < 0) {
setX(newX);
}
setWidth(Math.abs(offsetX));
double offsetY = newY - rectangleOriginY;
if (offsetY < 0) {
setY(newY);
}
setHeight(Math.abs(offsetY));
});
}
/**
* Reset the rectangle to default dimensions.
*/
public final void defaultSettings() {
setX(0);
setY(0);
setWidth(0);
setHeight(0);
setVisible(false);
}
}
}

View File

@ -0,0 +1,343 @@
/*
* 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.contentviewers.imagetagging;
import com.sun.javafx.event.EventDispatchChainImpl;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.collections.ListChangeListener;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
/**
* A tagged region displayed over an image. This class contains a "physical tag"
* and 8 edit "handles". The physical tag is a plain old rectangle that defines
* the tag boundaries. The edit handles serve two purposes. One is to represent
* selection. All 8 edit handles will become visible overtop the physical tag
* when the user clicks on the rectangle. The other purpose is to allow the user to edit
* and manipulate the physical tag boundaries (hence the name, edit handle).
* This class should be treated as a logical image tag.
*/
public final class ImageTag extends Group {
// Used to tell the 8 edit handles to hide if this tag is no longer selected
private final EventDispatchChainImpl ALL_CHILDREN;
//Notifies listeners that the user has editted the tag boundaries
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
//The underlying presistent tag details that this image tag originates from
private final ContentViewerTag<ImageTagRegion> appTag;
public ImageTag(ContentViewerTag<ImageTagRegion> contentViewerTag, ImageView image) {
ALL_CHILDREN = new EventDispatchChainImpl();
this.appTag = contentViewerTag;
this.getChildren().addListener((ListChangeListener<Node>) change -> {
change.next();
change.getAddedSubList().forEach((node) -> ALL_CHILDREN.append(node.getEventDispatcher()));
});
ImageTagRegion details = contentViewerTag.getDetails();
PhysicalTag physicalTag = new PhysicalTag(details);
//Defines the max allowable boundary that a user may drag any given handle.
Boundary dragBoundary = (x, y) -> {
double boundingX = image.getX();
double boundingY = image.getY();
double width = image.getImage().getWidth();
double height = image.getImage().getHeight();
return x > boundingX + details.getStrokeThickness() / 2
&& x < boundingX + width - details.getStrokeThickness() / 2
&& y > boundingY + details.getStrokeThickness() / 2
&& y < boundingY + height - details.getStrokeThickness() / 2;
};
EditHandle bottomLeft = new EditHandle(physicalTag)
.setPosition(Position.bottom(), Position.left())
.setDrag(dragBoundary, Draggable.bottom(), Draggable.left());
EditHandle bottomRight = new EditHandle(physicalTag)
.setPosition(Position.bottom(), Position.right())
.setDrag(dragBoundary, Draggable.bottom(), Draggable.right());
EditHandle topLeft = new EditHandle(physicalTag)
.setPosition(Position.top(), Position.left())
.setDrag(dragBoundary, Draggable.top(), Draggable.left());
EditHandle topRight = new EditHandle(physicalTag)
.setPosition(Position.top(), Position.right())
.setDrag(dragBoundary, Draggable.top(), Draggable.right());
EditHandle bottomMiddle = new EditHandle(physicalTag)
.setPosition(Position.bottom(), Position.xMiddle())
.setDrag(dragBoundary, Draggable.bottom());
EditHandle topMiddle = new EditHandle(physicalTag)
.setPosition(Position.top(), Position.xMiddle())
.setDrag(dragBoundary, Draggable.top());
EditHandle rightMiddle = new EditHandle(physicalTag)
.setPosition(Position.right(), Position.yMiddle())
.setDrag(dragBoundary, Draggable.right());
EditHandle leftMiddle = new EditHandle(physicalTag)
.setPosition(Position.left(), Position.yMiddle())
.setDrag(dragBoundary, Draggable.left());
//The "logical" tag is the Group
this.getChildren().addAll(physicalTag, bottomLeft, bottomRight, topLeft,
topRight, bottomMiddle, topMiddle, rightMiddle, leftMiddle);
Tooltip.install(this, new Tooltip(contentViewerTag.getContentTag()
.getName().getDisplayName()));
this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event));
this.addEventHandler(ImageTagControls.FOCUSED, event -> ALL_CHILDREN.dispatchEvent(event));
}
/**
* Add a new listener for edit events. These events are generated when a
* user drags on one of the edit "knobs" of the tag.
*
* @param listener
*/
public void subscribeToEditEvents(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
/**
* Get the content viewer tag that this class represents.
*
* @return
*/
public ContentViewerTag<ImageTagRegion> getContentViewerTag() {
return appTag;
}
/**
* Plain old rectangle that represents an unselected Image Tag
*/
class PhysicalTag extends Rectangle {
public PhysicalTag(ImageTagRegion details) {
this.setStroke(Color.RED);
this.setFill(Color.RED.deriveColor(0, 0, 0, 0));
this.setStrokeWidth(details.getStrokeThickness());
setX(details.getX());
setY(details.getY());
setWidth(details.getWidth());
setHeight(details.getHeight());
this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setOpacity(1));
this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setOpacity(0.5));
}
/**
* Builds a portable description of the tag region.
*
* @return
*/
public ImageTagRegion getState() {
return new ImageTagRegion()
.setX(this.getX())
.setY(this.getY())
.setWidth(this.getWidth())
.setHeight(this.getHeight())
.setStrokeThickness(this.getStrokeWidth());
}
}
/**
* Draggable "knob" used to manipulate the physical tag boundaries.
*/
class EditHandle extends Circle {
private final PhysicalTag parent;
public EditHandle(PhysicalTag parent) {
this.setVisible(false);
//Hide when the tag is not selected.
this.addEventHandler(ImageTagControls.NOT_FOCUSED, event -> this.setVisible(false));
this.addEventHandler(ImageTagControls.FOCUSED, event -> this.setVisible(true));
this.setRadius(parent.getStrokeWidth());
this.setFill(parent.getStroke());
this.setOnDragDetected(event -> {
this.getParent().setCursor(Cursor.CLOSED_HAND);
});
this.setOnMouseReleased(event -> {
this.getParent().setCursor(Cursor.DEFAULT);
pcs.firePropertyChange(new PropertyChangeEvent(this, "Tag Edit", null, parent.getState()));
});
this.parent = parent;
}
/**
* Sets the positioning of this edit handle on the physical tag.
*
* @param vals
* @return
*/
public EditHandle setPosition(Position... vals) {
for (Position pos : vals) {
parent.widthProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this));
parent.heightProperty().addListener((obs, oldVal, newVal) -> pos.set(parent, this));
pos.set(parent, this);
}
return this;
}
/**
* Sets the drag capabilities for manipulating the physical tag.
*
* @param bounds
* @param vals
* @return
*/
public EditHandle setDrag(Boundary bounds, Draggable... vals) {
this.setOnMouseDragged((event) -> {
for (Draggable drag : vals) {
drag.perform(parent, event, bounds);
}
});
return this;
}
}
/**
* Position strategies for "sticking" to a location on the physical tag when
* it is resized.
*/
static interface Position {
void set(PhysicalTag parent, Circle knob);
static Position left() {
return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty());
}
static Position right() {
return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth()));
}
static Position top() {
return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty());
}
static Position bottom() {
return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight()));
}
static Position xMiddle() {
return (parent, knob) -> knob.centerXProperty().bind(parent.xProperty().add(parent.getWidth() / 2));
}
static Position yMiddle() {
return (parent, knob) -> knob.centerYProperty().bind(parent.yProperty().add(parent.getHeight() / 2));
}
}
/**
* Defines the bounding box for which dragging is allowable.
*/
@FunctionalInterface
static interface Boundary {
boolean isPointInBounds(double x, double y);
}
/**
* Drag strategies for manipulating the physical tag from a given side of
* the rectangle.
*/
static interface Draggable {
void perform(PhysicalTag parent, MouseEvent event, Boundary b);
static Draggable bottom() {
return (parent, event, bounds) -> {
if (!bounds.isPointInBounds(event.getX(), event.getY())) {
return;
}
double deltaY = event.getY() - parent.getY();
if (deltaY > 0) {
parent.setHeight(deltaY);
}
};
}
static Draggable top() {
return (parent, event, bounds) -> {
if (!bounds.isPointInBounds(event.getX(), event.getY())) {
return;
}
double deltaY = parent.getY() + parent.getHeight() - event.getY();
if (deltaY < parent.getY() + parent.getHeight() && deltaY > 0) {
parent.setHeight(deltaY);
parent.setY(event.getY());
}
};
}
static Draggable left() {
return (parent, event, bounds) -> {
if (!bounds.isPointInBounds(event.getX(), event.getY())) {
return;
}
double deltaX = parent.getX() + parent.getWidth() - event.getX();
if (deltaX < parent.getX() + parent.getWidth() && deltaX > 0) {
parent.setWidth(deltaX);
parent.setX(event.getX());
}
};
}
static Draggable right() {
return (parent, event, bounds) -> {
if (!bounds.isPointInBounds(event.getX(), event.getY())) {
return;
}
double deltaX = event.getX() - parent.getX();
if (deltaX > 0) {
parent.setWidth(deltaX);
}
};
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.contentviewers.imagetagging;
import javafx.event.Event;
import javafx.event.EventType;
/**
* Focus events for ImageTags to consume. These events trigger selection behavior
* on ImageTags and are originated from the ImageTagsGroup class.
*/
public class ImageTagControls {
public static final EventType<Event> NOT_FOCUSED = new EventType<>("NOT_FOCUSED");
public static final EventType<Event> FOCUSED = new EventType<>("FOCUSED");
}

View File

@ -0,0 +1,180 @@
/*
* 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.contentviewers.imagetagging;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.event.EventHandler;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
/**
* Creates image tags. This class attaches itself to a source image, waiting
* for mouse press, mouse drag, and mouse release events. Upon a mouse release
* event, any listeners are updated with the portable description of the new tag
* boundaries (ImageTagRegion).
*/
public final class ImageTagCreator extends Rectangle {
//Origin of the drag event.
private double rectangleOriginX, rectangleOriginY;
//Rectangle lines should be 1.5% of the image. This level of thickness has
//a good balance between visual acuity and loss of selection at the borders
//of the image.
private final static double lineThicknessAsPercent = 1.5;
private final double minArea;
//Used to update listeners of the new tag boundaries
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private final EventHandler<MouseEvent> mousePressed;
private final EventHandler<MouseEvent> mouseDragged;
private final EventHandler<MouseEvent> mouseReleased;
//Handles the unregistering this ImageTagCreator from mouse press, mouse drag,
//and mouse release events of the source image.
private final Runnable disconnectRunnable;
/**
* Adds tagging support to an image, where the 'tag' rectangle will be the
* specified color.
*
* @param image Image to tag
*/
public ImageTagCreator(ImageView image) {
setStroke(Color.RED);
setFill(Color.RED.deriveColor(0, 0, 0, 0));
//Calculate how many pixels the stroke width should be to guarentee
//a consistent % of image consumed by the rectangle border.
double min = Math.min(image.getImage().getWidth(), image.getImage().getHeight());
double lineThicknessPixels = min * lineThicknessAsPercent / 100.0;
setStrokeWidth(lineThicknessPixels);
minArea = lineThicknessPixels * lineThicknessPixels;
setVisible(false);
this.mousePressed = (MouseEvent event) -> {
if (event.isSecondaryButtonDown()) {
return;
}
//Reset box on new click.
defaultSettings();
rectangleOriginX = event.getX();
rectangleOriginY = event.getY();
setX(rectangleOriginX);
setY(rectangleOriginY);
};
image.addEventHandler(MouseEvent.MOUSE_PRESSED, this.mousePressed);
this.mouseDragged = (MouseEvent event) -> {
if (event.isSecondaryButtonDown()) {
return;
}
double currentX = event.getX(), currentY = event.getY();
/**
* Ensure the rectangle is contained within image boundaries and
* that the line thickness is kept within bounds.
*/
double newX = Math.min(Math.max(currentX, image.getX())
+ lineThicknessPixels / 2, image.getImage().getWidth() - lineThicknessPixels / 2);
double newY = Math.min(Math.max(currentY, image.getY())
+ lineThicknessPixels / 2, image.getImage().getHeight() - lineThicknessPixels / 2);
setVisible(true);
double offsetX = newX - rectangleOriginX;
if (offsetX < 0) {
setX(newX);
}
setWidth(Math.abs(offsetX));
double offsetY = newY - rectangleOriginY;
if (offsetY < 0) {
setY(newY);
}
setHeight(Math.abs(offsetY));
};
image.addEventHandler(MouseEvent.MOUSE_DRAGGED, this.mouseDragged);
this.mouseReleased = event -> {
//Reject any drags that are too small to count as a meaningful tag.
//Meaningful is described as having an area that is visible that is
//not consumed by the thickness of the stroke.
if ((this.getWidth() - this.getStrokeWidth())
* (this.getHeight() - this.getStrokeWidth()) <= minArea) {
defaultSettings();
return;
}
this.pcs.firePropertyChange(new PropertyChangeEvent(this, "New Tag",
null, new ImageTagRegion()
.setX(this.getX())
.setY(this.getY())
.setWidth(this.getWidth())
.setHeight(this.getHeight())
.setStrokeThickness(lineThicknessPixels)));
};
image.addEventHandler(MouseEvent.MOUSE_RELEASED, this.mouseReleased);
//Used to remove itself from mouse events on the source image
disconnectRunnable = () -> {
defaultSettings();
image.removeEventHandler(MouseEvent.MOUSE_RELEASED, mouseReleased);
image.removeEventHandler(MouseEvent.MOUSE_DRAGGED, mouseDragged);
image.removeEventHandler(MouseEvent.MOUSE_PRESSED, mousePressed);
};
}
/**
* Registers a PCL for new tag events. Listeners are updated with a portable
* description (ImageTagRegion) of the new tag, which represent the
* rectangle boundaries.
*
* @param listener
*/
public void addNewTagListener(PropertyChangeListener listener) {
this.pcs.addPropertyChangeListener(listener);
}
/**
* Removes itself from mouse events on the source image.
*/
public void disconnect() {
this.disconnectRunnable.run();
}
/**
* Reset the rectangle to default dimensions.
*/
private void defaultSettings() {
setWidth(0);
setHeight(0);
setVisible(false);
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.contentviewers.imagetagging;
/**
* Bean representation of an image tag. This class is used for storage and
* retrieval of ImageTags from the case database.
*/
public class ImageTagRegion {
/**
* These fields will be serialized and stored in the case database by the
* ContentViewerTagManager.
*/
private double x;
private double y;
private double width;
private double height;
private double strokeThickness;
public ImageTagRegion setStrokeThickness(double thickness) {
this.strokeThickness = thickness;
return this;
}
public ImageTagRegion setX(double x) {
this.x = x;
return this;
}
public ImageTagRegion setWidth(double width) {
this.width = width;
return this;
}
public ImageTagRegion setY(double y) {
this.y = y;
return this;
}
public ImageTagRegion setHeight(double height) {
this.height = height;
return this;
}
public double getX() {
return x;
}
public double getWidth() {
return width;
}
public double getY() {
return y;
}
public double getHeight() {
return height;
}
public double getStrokeThickness() {
return strokeThickness;
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.contentviewers.imagetagging;
import com.sun.javafx.event.EventDispatchChainImpl;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javafx.event.Event;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
/**
* Manages the focus and z-ordering of ImageTags. Only one image tag may be
* selected at a time. Image tags show their 8 edit "handles" upon selection
* (see ImageTag class for more details) and get the highest z-ordering to make
* editing easier. This class is responsible for setting and dropping focus as
* the user navigates from tag to tag. The ImageTag is treated as a logical
* unit, however it's underlying representation consists of the physical
* rectangle and the 8 edit handles. JavaFX will report selection on the Node
* level (so either the Rectangle, or a singe edit handle), which makes keeping
* the entire image tag in focus a non-trivial problem.
*/
public final class ImageTagsGroup extends Group {
private final EventDispatchChainImpl NO_OP_CHAIN = new EventDispatchChainImpl();
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private volatile ImageTag currentFocus;
public ImageTagsGroup(Node backDrop) {
//Reset focus of current selection if the back drop has focus.
backDrop.setOnMousePressed((mouseEvent) -> {
if (currentFocus != null) {
currentFocus.getEventDispatcher().dispatchEvent(
new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN);
}
this.pcs.firePropertyChange(new PropertyChangeEvent(this,
ImageTagControls.NOT_FOCUSED.getName(), currentFocus, null));
currentFocus = null;
});
//Set the focus of selected tag
this.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> {
if (!e.isPrimaryButtonDown()) {
return;
}
//Pull out the logical image tag that this node is associated with
Node topLevelChild = e.getPickResult().getIntersectedNode();
while (!this.getChildren().contains(topLevelChild)) {
topLevelChild = topLevelChild.getParent();
}
requestFocus((ImageTag) topLevelChild);
});
}
/**
* Subscribe to focus change events on Image tags.
*
* @param fcl PCL to be notified which Image tag has been selected.
*/
public void addFocusChangeListener(PropertyChangeListener fcl) {
this.pcs.addPropertyChangeListener(fcl);
}
/**
* Get the image tag that current has focus.
*
* @return ImageTag instance or null if no tag is in focus.
*/
public ImageTag getFocus() {
return currentFocus;
}
/**
* Clears the current focus
*/
public void clearFocus() {
if(currentFocus != null) {
resetFocus(currentFocus);
currentFocus = null;
}
}
/**
* Notifies the logical image tag that it is no longer in focus.
*
* @param n
*/
private void resetFocus(ImageTag n) {
n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.NOT_FOCUSED), NO_OP_CHAIN);
this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.NOT_FOCUSED.getName(), n, null));
}
/**
* Notifies the logical image that it is in focus.
*
* @param n
*/
private void requestFocus(ImageTag n) {
if (n.equals(currentFocus)) {
return;
} else if (currentFocus != null && !currentFocus.equals(n)) {
resetFocus(currentFocus);
}
n.getEventDispatcher().dispatchEvent(new Event(ImageTagControls.FOCUSED), NO_OP_CHAIN);
this.pcs.firePropertyChange(new PropertyChangeEvent(this, ImageTagControls.FOCUSED.getName(), currentFocus, n));
currentFocus = n;
n.toFront();
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.contentviewers.imagetagging;
import java.util.Collection;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.Highgui;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Utility class for handling content viewer tags on images.
*/
public final class ImageTagsUtility {
/**
* Embeds the tag regions into an image (represented as an AbstractFile).
*
* @param file Base Image
* @param tagRegions Tag regions to be saved into the image
* @param outputEncoding Output file type encoding (ex. .jpg, .png)
* @return output image in byte array
* @throws TskCoreException
*/
public static byte[] exportTags(AbstractFile file, Collection<ImageTagRegion> tagRegions, String outputEncoding) throws TskCoreException {
byte[] imageInMemory = new byte[(int) file.getSize()];
file.read(imageInMemory, 0, file.getSize());
Mat originalImage = Highgui.imdecode(new MatOfByte(imageInMemory), Highgui.IMREAD_UNCHANGED);
tagRegions.forEach((region) -> {
Core.rectangle(
originalImage, //Matrix obj of the image
new Point(region.getX(), region.getY()), //p1
new Point(region.getX() + region.getWidth(), region.getY() + region.getHeight()), //p2
new Scalar(0, 0, 255), //Scalar object for color
(int) Math.rint(region.getStrokeThickness())
);
});
MatOfByte matOfByte = new MatOfByte();
Highgui.imencode(outputEncoding, originalImage, matOfByte);
originalImage.release();
byte[] output = matOfByte.toArray();
matOfByte.release();
return output;
}
private ImageTagsUtility(){
}
}

View File

@ -23,6 +23,7 @@ import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.datamodel.AbstractFile;
/**
* A DataContentViewer that displays text with the TextViewers available.
@ -90,6 +91,17 @@ public class TextContentViewer implements DataContentViewer {
if (node == null) {
return false;
}
// get the node's File, if it has one
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
if (file == null) {
return false;
}
// disable the text content viewer for directories and empty files
if (file.isDir() || file.getSize() == 0) {
return false;
}
return panel.isSupported(node);
}

View File

@ -79,7 +79,7 @@ public class TextContentViewerPanel extends javax.swing.JPanel implements DataCo
}
/**
* Deterime wether the content viewer which displays this panel isSupported.
* Determine whether the content viewer which displays this panel isSupported.
* This panel is supported if any of the TextViewer's displayed in it are
* supported.
*

View File

@ -504,10 +504,10 @@ public final class UserPreferences {
/**
* Get the maximum number of results to display in a result table.
*
* @return Saved value or default (0) which indicates no max.
* @return Saved value or default (10,000).
*/
public static int getResultsTablePageSize() {
return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 0);
return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 10_000);
}
/**

View File

@ -216,4 +216,5 @@ 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>
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nSetting this value to 0 will display all results in the results table.\n<br>Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n</html>
DataResultViewerTable.exportCSVButton.text=Save table as CSV

View File

@ -32,6 +32,7 @@ DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found
DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)
DataResultViewerTable.countRender.name=O
DataResultViewerTable.countRender.toolTip=O(ccurrences) indicates the number of data sources containing the item in the Central Repository
DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export
DataResultViewerTable.firstColLbl=Name
DataResultViewerTable.goToPageTextField.err=Invalid page number
# {0} - totalPages
@ -269,4 +270,5 @@ 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>
ViewPreferencesPanel.maxResultsLabel.toolTipText=<html>\nSetting this value to 0 will display all results in the results table.\n<br>Note that setting this value to 0 may result in poor UI responsiveness when there are large numbers of results.\n</html>
DataResultViewerTable.exportCSVButton.text=Save table as CSV

View File

@ -16,13 +16,13 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" 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="outlineView" pref="904" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="pageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pageNumLabel" min="-2" pref="53" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="14" 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"/>
@ -32,14 +32,15 @@
<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"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="exportCSVButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" pref="3" 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"/>
@ -48,9 +49,10 @@
<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"/>
<Component id="exportCSVButton" 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 min="-2" pref="3" max="-2" attributes="0"/>
<Component id="outlineView" pref="321" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
@ -164,5 +166,15 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="gotoPageTextFieldActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="exportCSVButton">
<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.exportCSVButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportCSVButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -77,6 +77,7 @@ 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.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
@ -176,6 +177,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
initComponents();
initializePagingSupport();
/*
* Disable the CSV export button for the common properties results
*/
if (this instanceof org.sleuthkit.autopsy.commonpropertiessearch.CommonAttributesSearchResultsViewerTable) {
exportCSVButton.setEnabled(false);
}
/*
* Configure the child OutlineView (explorer view) component.
@ -1291,6 +1299,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL);
gotoPageLabel = new javax.swing.JLabel();
gotoPageTextField = new javax.swing.JTextField();
exportCSVButton = new javax.swing.JButton();
pageLabel.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.pageLabel.text")); // NOI18N
@ -1338,17 +1347,24 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
});
exportCSVButton.setText(org.openide.util.NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.exportCSVButton.text")); // NOI18N
exportCSVButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
exportCSVButtonActionPerformed(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, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap(608, Short.MAX_VALUE)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 904, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(pageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pageNumLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGap(14, 14, 14)
.addComponent(pagesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(pagePrevButton, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -1358,7 +1374,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
.addComponent(gotoPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 33, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(exportCSVButton))
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {pageNextButton, pagePrevButton});
@ -1366,7 +1383,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGap(3, 3, 3)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
.addComponent(pageLabel)
.addComponent(pageNumLabel)
@ -1374,9 +1391,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
.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)
.addComponent(gotoPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(exportCSVButton))
.addGap(3, 3, 3)
.addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 321, Short.MAX_VALUE)
.addContainerGap())
);
@ -1397,7 +1415,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
pagingSupport.gotoPage();
}//GEN-LAST:event_gotoPageTextFieldActionPerformed
@NbBundle.Messages({"DataResultViewerTable.exportCSVButtonActionPerformed.empty=No data to export"
})
private void exportCSVButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCSVButtonActionPerformed
Node currentRoot = this.getExplorerManager().getRootContext();
if (currentRoot != null && currentRoot.getChildren().getNodesCount() > 0) {
org.sleuthkit.autopsy.directorytree.ExportCSVAction.saveNodesToCSV(java.util.Arrays.asList(currentRoot.getChildren().getNodes()), this);
} else {
MessageNotifyUtil.Message.info(Bundle.DataResultViewerTable_exportCSVButtonActionPerformed_empty());
}
}//GEN-LAST:event_exportCSVButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton exportCSVButton;
private javax.swing.JLabel gotoPageLabel;
private javax.swing.JTextField gotoPageTextField;
private org.openide.explorer.view.OutlineView outlineView;

View File

@ -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");
@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.datamodel;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
@ -27,8 +26,6 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
@ -65,6 +62,7 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -76,15 +74,10 @@ import org.sleuthkit.datamodel.TskData;
public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends AbstractContentNode<T> {
private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName());
@NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description")
private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
private static final ExecutorService translationPool;
private static final Integer MAX_POOL_SIZE = 10;
/**
* @param abstractFile file to wrap
*/
@ -101,7 +94,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
}
if (UserPreferences.displayTranslatedFileNames()) {
AbstractAbstractFileNode.translationPool.submit(new TranslationTask(
backgroundTasksPool.submit(new TranslationTask(
new WeakReference<>(this), weakPcl));
}
@ -109,14 +102,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
// or when tags are added.
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
static {
//Initialize this pool only once! This will be used by every instance of AAFN
//to do their heavy duty SCO column and translation updates.
translationPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
new ThreadFactoryBuilder().setNameFormat("translation-task-thread-%d").build());
}
/**
* The finalizer removes event listeners as the BlackboardArtifactNode is
* being garbage collected. Yes, we know that finalizers are considered to
@ -137,16 +123,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
}
/**
* Event signals to indicate the background tasks have completed processing.
* Currently, we have one property task in the background:
*
* 1) Retreiving the translation of the file name
*/
enum NodeSpecificEvents {
TRANSLATION_AVAILABLE,
}
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
String eventType = evt.getPropertyName();
@ -190,7 +167,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
} else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
if (event.getAddedTag().getContent().equals(content)) {
List<ContentTag> tags = getContentTagsFromDatabase();
List<Tag> tags = this.getAllTagsFromDatabase();
Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
Score value = scorePropAndDescr.getLeft();
String descr = scorePropAndDescr.getRight();
@ -202,7 +179,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
} else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
List<ContentTag> tags = getContentTagsFromDatabase();
List<Tag> tags = getAllTagsFromDatabase();
Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
Score value = scorePropAndDescr.getLeft();
String descr = scorePropAndDescr.getRight();
@ -214,7 +191,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
CommentChangedEvent event = (CommentChangedEvent) evt;
if (event.getContentID() == content.getId()) {
List<ContentTag> tags = getContentTagsFromDatabase();
List<Tag> tags = getAllTagsFromDatabase();
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute)));
}
@ -223,6 +200,18 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
//Set the tooltip
this.setShortDescription(content.getName());
updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName()));
} else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString())) {
SCOData scoData = (SCOData)evt.getNewValue();
if (scoData.getScoreAndDescription() != null) {
updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
}
if (scoData.getComment() != null) {
updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment()));
}
if (scoData.getCountAndDescription() != null &&
!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
}
}
};
/**
@ -235,38 +224,6 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
*/
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
/**
* Updates the values of the properties in the current property sheet with
* the new properties being passed in. Only if that property exists in the
* current sheet will it be applied. That way, we allow for subclasses to
* add their own (or omit some!) properties and we will not accidentally
* disrupt their UI.
*
* Race condition if not synchronized. Only one update should be applied at
* a time.
*
* @param newProps New file property instances to be updated in the current
* sheet.
*/
private synchronized void updateSheet(NodeProperty<?>... newProps) {
//Refresh ONLY those properties in the sheet currently. Subclasses may have
//only added a subset of our properties or their own props.s
Sheet visibleSheet = this.getSheet();
Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
Property<?>[] visibleProps = visibleSheetSet.getProperties();
for (NodeProperty<?> newProp : newProps) {
for (int i = 0; i < visibleProps.length; i++) {
if (visibleProps[i].getName().equals(newProp.getName())) {
visibleProps[i] = newProp;
}
}
}
visibleSheetSet.put(visibleProps);
visibleSheet.put(visibleSheetSet);
//setSheet() will notify Netbeans to update this node in the UI.
this.setSheet(visibleSheet);
}
/*
* This is called when the node is first initialized. Any new updates or
* changes happen by directly manipulating the sheet. That means we can fire
@ -368,18 +325,18 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, ""));
}
//SCO column prereq info..
List<ContentTag> tags = getContentTagsFromDatabase();
CorrelationAttributeInstance attribute = getCorrelationAttributeInstance();
Pair<DataResultViewerTable.Score, String> scoreAndDescription = getScorePropertyAndDescription(tags);
properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoreAndDescription.getRight(), scoreAndDescription.getLeft()));
DataResultViewerTable.HasCommentStatus comment = getCommentProperty(tags, attribute);
properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, comment));
if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
Pair<Long, String> countAndDescription = getCountPropertyAndDescription(attribute);
properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), countAndDescription.getRight(), countAndDescription.getLeft()));
// Create place holders for S C O
properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, ""));
properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, ""));
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, ""));
}
// Get the SCO columns data in a background task
backgroundTasksPool.submit(new GetSCOTask(
new WeakReference<>(this), weakPcl));
properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content)));
properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content)));
@ -441,7 +398,8 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
"# {0} - occuranceCount",
"AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
@Override
protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
try {
@ -468,7 +426,8 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<ContentTag> tags) {
@Override
protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE;
String description = Bundle.AbstractAbstractFileNode_createSheet_noScore_description();
if (content.getKnown() == TskData.FileKnown.BAD) {
@ -486,7 +445,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
if (!tags.isEmpty() && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) {
score = DataResultViewerTable.Score.INTERESTING_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
for (ContentTag tag : tags) {
for (Tag tag : tags) {
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
score = DataResultViewerTable.Score.NOTABLE_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
@ -499,11 +458,12 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
@NbBundle.Messages({
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
HasCommentStatus getCommentProperty(List<ContentTag> tags, CorrelationAttributeInstance attribute) {
@Override
protected HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
DataResultViewerTable.HasCommentStatus status = !tags.isEmpty() ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT;
for (ContentTag tag : tags) {
for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
//if the tag is null or empty or contains just white space it will indicate there is not a comment
status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT;
@ -571,7 +531,13 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
return tags;
}
CorrelationAttributeInstance getCorrelationAttributeInstance() {
@Override
protected List<Tag> getAllTagsFromDatabase() {
return new ArrayList<>(getContentTagsFromDatabase());
}
@Override
protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance attribute = null;
if (EamDb.isEnabled() && !UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
attribute = EamArtifactUtil.getInstanceFromContent(content);

View File

@ -18,20 +18,29 @@
*/
package org.sleuthkit.autopsy.datamodel;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Children;
import org.openide.nodes.Sheet;
import org.openide.util.lookup.Lookups;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException;
@ -50,6 +59,38 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
T content;
private static final Logger logger = Logger.getLogger(AbstractContentNode.class.getName());
/**
* A pool of background tasks to run any long computation needed to
* populate this node.
*/
static final ExecutorService backgroundTasksPool;
private static final Integer MAX_POOL_SIZE = 10;
/**
* Default no description string
*/
@NbBundle.Messages({"AbstractContentNode.nodescription=no description",
"AbstractContentNode.valueLoading=value loading"})
protected static final String NO_DESCR = Bundle.AbstractContentNode_nodescription();
protected static final String VALUE_LOADING = Bundle.AbstractContentNode_valueLoading();
/**
* Event signals to indicate the background tasks have completed processing.
* Currently, we have one property task in the background:
*
* 1) Retrieving the translation of the file name
*/
enum NodeSpecificEvents {
TRANSLATION_AVAILABLE,
SCO_AVAILABLE
}
static {
//Initialize this pool only once! This will be used by every instance of AAFN
//to do their heavy duty SCO column and translation updates.
backgroundTasksPool = Executors.newFixedThreadPool(MAX_POOL_SIZE,
new ThreadFactoryBuilder().setNameFormat("content-node-background-task-%d").build());
}
/**
* Handles aspects that depend on the Content object
*
@ -240,4 +281,79 @@ public abstract class AbstractContentNode<T extends Content> extends ContentNode
public int read(byte[] buf, long offset, long len) throws TskException {
return content.read(buf, offset, len);
}
/**
* Updates the values of the properties in the current property sheet with
* the new properties being passed in. Only if that property exists in the
* current sheet will it be applied. That way, we allow for subclasses to
* add their own (or omit some!) properties and we will not accidentally
* disrupt their UI.
*
* Race condition if not synchronized. Only one update should be applied at
* a time.
*
* @param newProps New file property instances to be updated in the current
* sheet.
*/
protected synchronized void updateSheet(NodeProperty<?>... newProps) {
//Refresh ONLY those properties in the sheet currently. Subclasses may have
//only added a subset of our properties or their own props.s
Sheet visibleSheet = this.getSheet();
Sheet.Set visibleSheetSet = visibleSheet.get(Sheet.PROPERTIES);
Property<?>[] visibleProps = visibleSheetSet.getProperties();
for (NodeProperty<?> newProp : newProps) {
for (int i = 0; i < visibleProps.length; i++) {
if (visibleProps[i].getName().equals(newProp.getName())) {
visibleProps[i] = newProp;
}
}
}
visibleSheetSet.put(visibleProps);
visibleSheet.put(visibleSheetSet);
//setSheet() will notify Netbeans to update this node in the UI.
this.setSheet(visibleSheet);
}
/**
* Reads and returns a list of all tags associated with this content node.
*
* @return list of tags associated with the node.
*/
abstract protected List<Tag> getAllTagsFromDatabase();
/**
* Returns correlation attribute instance for the underlying content of the node.
*
* @return correlation attribute instance for the underlying content of the node.
*/
abstract protected CorrelationAttributeInstance getCorrelationAttributeInstance();
/**
* Returns Score property for the node.
*
* @param tags list of tags.
*
* @return Score property for the underlying content of the node.
*/
abstract protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags);
/**
* Returns comment property for the node.
*
* @param tags list of tags
* @param attribute correlation attribute instance
*
* @return Comment property for the underlying content of the node.
*/
abstract protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute);
/**
* Returns occurrences/count property for the node.
*
* @param attribute correlation attribute instance
*
* @return count property for the underlying content of the node.
*/
abstract protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute);
}

View File

@ -286,7 +286,12 @@ public abstract class BaseChildFactory<T extends Content> extends ChildFactory.D
* 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 (keys.isEmpty()) {
pages.clear();
} else {
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.

View File

@ -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 com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@ -37,6 +38,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.Action;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
@ -55,11 +57,13 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.backgroundTasksPool;
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager;
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
@ -95,10 +99,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
private Content associated = null;
private List<NodeProperty<? extends Object>> customProperties;
private final static String NO_DESCR = NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.noDesc.text");
/*
* Artifact types which should have the full unique path of the associated
* content as a property.
@ -151,6 +152,19 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
contentCache.invalidateAll();
}
}
else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString())) {
SCOData scoData = (SCOData)evt.getNewValue();
if (scoData.getScoreAndDescription() != null) {
updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
}
if (scoData.getComment() != null) {
updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, scoData.getComment()));
}
if (scoData.getCountAndDescription() != null &&
!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
}
}
}
};
@ -335,8 +349,6 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
List<Tag> tags = getAllTagsFromDatabase();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@ -351,17 +363,17 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
NO_DESCR,
this.getSourceName()));
addScoreProperty(sheetSet, tags);
CorrelationAttributeInstance correlationAttribute = null;
// Create place holders for S C O
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, ""));
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, ""));
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
correlationAttribute = getCorrelationAttributeInstance();
}
addCommentProperty(sheetSet, tags, correlationAttribute);
if (UserPreferences.hideCentralRepoCommentsAndOccurrences() == false) {
addCountProperty(sheetSet, correlationAttribute);
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, ""));
}
// Get the SCO columns data in a background task
backgroundTasksPool.submit(new GetSCOTask(
new WeakReference<>(this), weakPcl));
if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
try {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
@ -520,6 +532,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
* @return a list of tags which on the artifact or the file it is associated
* with
*/
@Override
protected final List<Tag> getAllTagsFromDatabase() {
List<Tag> tags = new ArrayList<>();
try {
@ -569,6 +582,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", "))));
}
@Override
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance correlationAttribute = null;
if (EamDb.isEnabled()) {
@ -581,16 +595,36 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
* Used by (subclasses of) BlackboardArtifactNode to add the comment
* property to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param sheetSet the modifiable Sheet.Set to add the property to
* @param tags the list of tags associated with the file
* @param attribute the correlation attribute associated with this
* artifact's associated file, null if central repo is not
* enabled
*
* @deprecated Use the GetSCOTask to get this data on a background thread...,
* and then update the property sheet asynchronously
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C",
"BlackboardArtifactNode.createSheet.comment.displayName=C"})
@Deprecated
protected final void addCommentProperty(Sheet.Set sheetSet, List<Tag> tags, CorrelationAttributeInstance attribute) {
HasCommentStatus status = getCommentProperty(tags, attribute );
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR,
status));
}
/**
* Gets the comment property for the node
*
* @param tags the list of tags associated with the file
* @param attribute the correlation attribute associated with this
* artifact's associated file, null if central repo is not
* enabled
* @return comment property
*/
@Override
protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
@ -609,17 +643,17 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
status = HasCommentStatus.CR_COMMENT;
}
}
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR,
status));
return status;
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the Score property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param sheetSet the modifiable Sheet.Set to add the property to
* @param tags the list of tags associated with the file
*
* @deprecated Use the GetSCOTask to get this data on a background thread...,
* and then update the property sheet asynchronously
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S",
"BlackboardArtifactNode.createSheet.score.displayName=S",
@ -628,8 +662,22 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
"BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged.",
"BlackboardArtifactNode.createSheet.notableTaggedItem.description=Result or associated file tagged with notable tag.",
"BlackboardArtifactNode.createSheet.noScore.description=No score"})
protected final void addScoreProperty(Sheet.Set sheetSet, List<Tag> tags) {
Score score = Score.NO_SCORE;
@Deprecated
protected final void addScorePropertyAndDescription(Sheet.Set sheetSet, List<Tag> tags) {
Pair<DataResultViewerTable.Score, String> scoreAndDescription = getScorePropertyAndDescription(tags);
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoreAndDescription.getRight(), scoreAndDescription.getLeft()));
}
/**
* Get the score property for the node.
*
* @param tags the list of tags associated with the file
*
* @return score property and description
*/
@Override
protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
Score score = Score.NO_SCORE;
String description = Bundle.BlackboardArtifactNode_createSheet_noScore_description();
if (associated instanceof AbstractFile) {
if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) {
@ -673,18 +721,43 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
}
}
}
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score));
return Pair.of(score, description);
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the Occurrences property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set to add the property to
* @param attribute correlation attribute instance
*
* @deprecated Use the GetSCOTask to get this data on a background thread...,
* and then update the property sheet asynchronously
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O",
"BlackboardArtifactNode.createSheet.count.displayName=O",
"BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
"BlackboardArtifactNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this artifact's associated file when the column was populated",
"# {0} - occuranceCount",
"BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
@Deprecated
protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) {
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
Pair<Long, String> countAndDescription = getCountPropertyAndDescription(attribute);
sheetSet.put(
new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), countAndDescription.getRight(), countAndDescription.getLeft()));
}
/**
* Gets the Occurrences property for the node.
*
* @param attribute correlation attribute instance
*
* @return count and description
*
*/
@Override
protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
Long count = -1L;
String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description();
try {
//don't perform the query if there is no correlation value
@ -699,14 +772,13 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
sheetSet.put(
new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), description, count));
}
return Pair.of(count, description);
}
private void updateSheet() {
this.setSheet(createSheet());
}
private String getRootParentName() {
String parentName = associated.getName();
Content parent = associated;

View File

@ -1,5 +1,4 @@
AbstractAbstractFileNode.accessTimeColLbl=Access Time
AbstractAbstractFileNode.addFileProperty.desc=no description
AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.
AbstractAbstractFileNode.changeTimeColLbl=Change Time
AbstractAbstractFileNode.createdTimeColLbl=Created Time
@ -37,6 +36,8 @@ AbstractAbstractFileNode.tagsProperty.displayName=Tags
AbstractAbstractFileNode.typeDirColLbl=Type(Dir)
AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)
AbstractAbstractFileNode.useridColLbl=UserID
AbstractContentNode.nodescription=no description
AbstractContentNode.valueLoading=value loading
AbstractFsContentNode.noDesc.text=no description
ArtifactStringContent.attrsTableHeader.sources=Source(s)
ArtifactStringContent.attrsTableHeader.type=Type

View File

@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.actions.ReplaceBlackboardArtifactTagAction;
import org.sleuthkit.autopsy.actions.ReplaceContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.datamodel.Reports.ReportNode;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@ -92,6 +93,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -119,6 +121,7 @@ public class DataModelActionsFactory {
actionsList.add(new NewWindowViewAction(VIEW_IN_NEW_WINDOW, slackFileNode));
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -155,6 +158,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());//
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -189,6 +193,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -223,6 +228,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -257,6 +263,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -291,6 +298,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -325,6 +333,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -379,6 +388,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {
@ -415,6 +425,7 @@ public class DataModelActionsFactory {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
if (isArtifactSource) {

View File

@ -28,6 +28,7 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
@ -89,6 +90,7 @@ public class DirectoryNode extends AbstractFsContentNode<AbstractFile> {
actionsList.add(ViewFileInTimelineAction.createViewFileAction(content));
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(new RunIngestModulesAction(content));
actionsList.add(null); // creates a menu separator

View File

@ -32,6 +32,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@ -173,6 +174,7 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
actionsList.add(null); // Creates an item separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // Creates an item separator
actionsList.add(AddContentTagAction.getInstance());

View File

@ -390,6 +390,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem {
* @param skCase
* @param o Observable that will notify when there could be new
* data to display
* @param nodeName
*/
private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o, String nodeName) {
super(nodeName, new ViewsKnownAndSlackFilter<>());

View File

@ -0,0 +1,73 @@
/*
* 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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.List;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.datamodel.Tag;
/**
* Background task to get Score, Comment and Occurrences values for an
* Abstract content node.
*
*/
class GetSCOTask implements Runnable {
private final WeakReference<AbstractContentNode<?>> weakNodeRef;
private final PropertyChangeListener listener;
GetSCOTask(WeakReference<AbstractContentNode<?>> weakContentRef, PropertyChangeListener listener) {
this.weakNodeRef = weakContentRef;
this.listener = listener;
}
@Override
public void run() {
AbstractContentNode<?> contentNode = weakNodeRef.get();
//Check for stale reference
if (contentNode == null) {
return;
}
// get the SCO column values
List<Tag> tags = contentNode.getAllTagsFromDatabase();
CorrelationAttributeInstance attribute = contentNode.getCorrelationAttributeInstance();
SCOData scoData = new SCOData();
scoData.setScoreAndDescription(contentNode.getScorePropertyAndDescription(tags));
scoData.setComment(contentNode.getCommentProperty(tags, attribute));
if (!UserPreferences.hideCentralRepoCommentsAndOccurrences()) {
scoData.setCountAndDescription(contentNode.getCountPropertyAndDescription(attribute));
}
// signal SCO data is available.
if (listener != null) {
listener.propertyChange(new PropertyChangeEvent(
AutopsyEvent.SourceType.LOCAL.toString(),
AbstractAbstractFileNode.NodeSpecificEvents.SCO_AVAILABLE.toString(),
null, scoData));
}
}
}

View File

@ -28,12 +28,15 @@ import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
@ -47,6 +50,7 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
import org.sleuthkit.datamodel.Tag;
/**
* This class is used to represent the "Node" for the image. The children of
@ -250,4 +254,67 @@ public class ImageNode extends AbstractContentNode<Image> {
}
};
/**
* Reads and returns a list of all tags associated with this content node.
*
* Null implementation of an abstract method.
*
* @return list of tags associated with the node.
*/
@Override
protected List<Tag> getAllTagsFromDatabase() {
return new ArrayList<>();
}
/**
* Returns correlation attribute instance for the underlying content of the node.
*
* Null implementation of an abstract method.
*
* @return correlation attribute instance for the underlying content of the node.
*/
@Override
protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
return null;
}
/**
* Returns Score property for the node.
*
* Null implementation of an abstract method.
*
* @param tags list of tags.
*
* @return Score property for the underlying content of the node.
*/
@Override
protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR);
}
/**
* Returns comment property for the node.
*
* Null implementation of an abstract method.
*
* @param tags list of tags
* @param attribute correlation attribute instance
*
* @return Comment property for the underlying content of the node.
*/
@Override
protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
}
/**
* Returns occurrences/count property for the node.
*
* Null implementation of an abstract method.
*
* @param attribute correlation attribute instance
*
* @return count property for the underlying content of the node.
*/
@Override
protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
return Pair.of(-1L, NO_DESCR);
}
}

View File

@ -29,6 +29,7 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@ -105,6 +106,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());

View File

@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerShortcutAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
@ -82,6 +83,7 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());

View File

@ -0,0 +1,56 @@
/*
* 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 org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
/**
* Container to bag the S C & O data for an abstract file node.
*
*/
class SCOData {
private Pair<DataResultViewerTable.Score, String> scoreAndDescription = null;
private DataResultViewerTable.HasCommentStatus comment = null;
private Pair<Long, String> countAndDescription = null;
Pair<DataResultViewerTable.Score, String> getScoreAndDescription() {
return scoreAndDescription;
}
DataResultViewerTable.HasCommentStatus getComment() {
return comment;
}
Pair<Long, String> getCountAndDescription() {
return countAndDescription;
}
void setScoreAndDescription(Pair<DataResultViewerTable.Score, String> scoreAndDescription) {
this.scoreAndDescription = scoreAndDescription;
}
void setComment(DataResultViewerTable.HasCommentStatus comment) {
this.comment = comment;
}
void setCountAndDescription(Pair<Long, String> countAndDescription) {
this.countAndDescription = countAndDescription;
}
}

View File

@ -28,6 +28,7 @@ import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
@ -85,6 +86,7 @@ public class SlackFileNode extends AbstractFsContentNode<AbstractFile> {
NbBundle.getMessage(this.getClass(), "SlackFileNode.getActions.viewInNewWin.text"), this));
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());

View File

@ -25,6 +25,7 @@ import javax.swing.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.ViewSummaryInformationAction;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
@ -61,6 +62,7 @@ public abstract class SpecialDirectoryNode extends AbstractAbstractFileNode<Spec
Bundle.SpecialDirectoryNode_getActions_viewInNewWin_text(), this));
actions.add(null); // creates a menu separator
actions.add(ExtractAction.getInstance());
actions.add(ExportCSVAction.getInstance());
actions.add(null); // creates a menu separator
actions.add(new FileSearchAction(Bundle.ImageNode_getActions_openFileSearchByAttr_text()));
if (content.isDataSource()) {

View File

@ -25,10 +25,14 @@ import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR;
import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
@ -39,6 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.Volume;
import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction;
import org.sleuthkit.datamodel.Tag;
/**
* This class is used to represent the "Node" for the volume. Its child is the
@ -212,4 +217,67 @@ public class VolumeNode extends AbstractContentNode<Volume> {
public String getItemType() {
return DisplayableItemNode.FILE_PARENT_NODE_KEY;
}
/**
* Reads and returns a list of all tags associated with this content node.
*
* Null implementation of an abstract method.
*
* @return list of tags associated with the node.
*/
@Override
protected List<Tag> getAllTagsFromDatabase() {
return new ArrayList<>();
}
/**
* Returns correlation attribute instance for the underlying content of the node.
*
* Null implementation of an abstract method.
*
* @return correlation attribute instance for the underlying content of the node.
*/
@Override
protected CorrelationAttributeInstance getCorrelationAttributeInstance() {
return null;
}
/**
* Returns Score property for the node.
*
* Null implementation of an abstract method.
*
* @param tags list of tags.
*
* @return Score property for the underlying content of the node.
*/
@Override
protected Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(List<Tag> tags) {
return Pair.of(DataResultViewerTable.Score.NO_SCORE, NO_DESCR);
}
/**
* Returns comment property for the node.
*
* Null implementation of an abstract method.
*
* @param tags list of tags
* @param attribute correlation attribute instance
*
* @return Comment property for the underlying content of the node.
*/
@Override
protected DataResultViewerTable.HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
return DataResultViewerTable.HasCommentStatus.NO_COMMENT;
}
/**
* Returns occurrences/count property for the node.
*
* Null implementation of an abstract method.
*
* @param attribute correlation attribute instance
*
* @return count property for the underlying content of the node.
*/
@Override
protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
return Pair.of(-1L, NO_DESCR);
}
}

View File

@ -1,3 +1,8 @@
CSVWriter.done.notifyMsg.error=Error exporting to CSV file
# {0} - Output file
CSVWriter.done.notifyMsg.success=Wrote to {0}
CSVWriter.progress.cancelling=Cancelling
CSVWriter.progress.extracting=Exporting to CSV file
CTL_DirectoryTreeTopComponent=Directory Tree
DataResultFilterNode.viewSourceArtifact.text=View Source Result
# {0} - dataSourceCount
@ -5,6 +10,11 @@ DirectoryTreeTopComponent.componentOpened.groupDataSources.text=This case contai
DirectoryTreeTopComponent.componentOpened.groupDataSources.title=Group by data source?
DirectoryTreeTopComponent.emptyMimeNode.text=Data not available. Run file type identification module.
DirectoryTreeTopComponent.resultsView.title=Listing
ExportCSV.saveNodesToCSV.empty=No data to export
# {0} - Output file
ExportCSV.saveNodesToCSV.fileExists=File {0} already exists
ExportCSV.saveNodesToCSV.noCurrentCase=No open case available
ExportCSV.title.text=Export selected rows to CSV
ExternalViewerAction.actionPerformed.failure.exe.message=The file is an executable and will not be opened.
ExternalViewerAction.actionPerformed.failure.IO.message=There is no associated editor for files of this type or the associated application failed to launch.
ExternalViewerAction.actionPerformed.failure.missingFile.message=The file no longer exists.

View File

@ -372,6 +372,7 @@ public class DataResultFilterNode extends FilterNode {
}
actionsList.add(null); // creates a menu separator
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());
actionsList.add(AddBlackboardArtifactTagAction.getInstance());

View File

@ -125,6 +125,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? ext
}
}
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.addAll(ContextMenuExtensionPoint.getActions());
return actionsList;
}
@ -142,6 +143,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? ext
}
}
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.addAll(ContextMenuExtensionPoint.getActions());
return actionsList;
}
@ -150,6 +152,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? ext
public List<? extends Action> visit(final DerivedFile d) {
List<Action> actionsList = new ArrayList<>();
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(AddContentTagAction.getInstance());
final Collection<AbstractFile> selectedFilesList =
@ -166,6 +169,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? ext
public List<? extends Action> visit(final LocalFile d) {
List<Action> actionsList = new ArrayList<>();
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(AddContentTagAction.getInstance());
final Collection<AbstractFile> selectedFilesList =
@ -182,6 +186,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? ext
public List<? extends Action> visit(final org.sleuthkit.datamodel.File d) {
List<Action> actionsList = new ArrayList<>();
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(AddContentTagAction.getInstance());
final Collection<AbstractFile> selectedFilesList =

View File

@ -0,0 +1,360 @@
/*
* 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 content 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.directorytree;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType;
import org.openide.nodes.Node;
import org.openide.nodes.Node.PropertySet;
import org.openide.nodes.Node.Property;
/**
* Exports CSV version of result nodes to a location selected by the user.
*/
public final class ExportCSVAction extends AbstractAction {
private static final Logger logger = Logger.getLogger(ExportCSVAction.class.getName());
private final static String DEFAULT_FILENAME = "Results";
private final static List<String> columnsToSkip = Arrays.asList(AbstractFilePropertyType.SCORE.toString(),
AbstractFilePropertyType.COMMENT.toString(), AbstractFilePropertyType.OCCURRENCES.toString());
private static String userDefinedExportPath;
// This class is a singleton to support multi-selection of nodes, since
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
// node in the array returns a reference to the same action object from Node.getActions(boolean).
private static ExportCSVAction instance;
/**
* Get an instance of the Action. See above for why
* the class is a singleton.
*
* @return the instance
*/
public static synchronized ExportCSVAction getInstance() {
if (null == instance) {
instance = new ExportCSVAction();
}
return instance;
}
/**
* Private constructor for the action.
*/
@NbBundle.Messages({"ExportCSV.title.text=Export selected rows to CSV"})
private ExportCSVAction() {
super(Bundle.ExportCSV_title_text());
}
/**
* Asks user to choose destination, then extracts content to destination
* (recursing on directories).
*
* @param e The action event.
*/
@Override
public void actionPerformed(ActionEvent e) {
Collection<? extends Node> selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class);
saveNodesToCSV(selectedNodes, (Component)e.getSource());
}
/**
* Save the selected nodes to a CSV file
*
* @param nodesToExport the nodes to save
* @param component
*/
@NbBundle.Messages({
"# {0} - Output file",
"ExportCSV.saveNodesToCSV.fileExists=File {0} already exists",
"ExportCSV.saveNodesToCSV.noCurrentCase=No open case available",
"ExportCSV.saveNodesToCSV.empty=No data to export"})
public static void saveNodesToCSV(Collection<? extends Node> nodesToExport, Component component) {
if (nodesToExport.isEmpty()) {
MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_empty());
return;
}
try {
// Set up the file chooser with a default name and either the Export
// folder or the last used folder.
String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode());
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows())));
fileChooser.setSelectedFile(new File(fileName));
fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv"));
int returnVal = fileChooser.showSaveDialog(component);
if (returnVal == JFileChooser.APPROVE_OPTION) {
// Get the file name, appending .csv if necessary
File selectedFile = fileChooser.getSelectedFile();
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
}
// Save the directory used for next time
updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows());
if (selectedFile.exists()) {
logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS
MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_fileExists(selectedFile));
return;
}
CSVWriter writer = new CSVWriter(nodesToExport, selectedFile);
writer.execute();
}
} catch (NoCurrentCaseException ex) {
JOptionPane.showMessageDialog(component, Bundle.ExportCSV_saveNodesToCSV_noCurrentCase());
logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
}
}
/**
* Create a default name for the CSV output.
*
* @param parent The parent node for the selected nodes
*
* @return the default name
*/
private static String getDefaultOutputFileName(Node parent) {
String dateStr = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS", Calendar.getInstance());
if (parent != null) {
// The first value in the property set is generally a reasonable name
for (PropertySet set : parent.getPropertySets()) {
for (Property<?> prop : set.getProperties()) {
try {
String parentName = prop.getValue().toString();
// Strip off the count (if present)
parentName = parentName.replaceAll("\\([0-9]+\\)$", "");
// Strip out any invalid characters
parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_");
return parentName + " " + dateStr;
} catch (IllegalAccessException | InvocationTargetException ex) {
logger.log(Level.WARNING, "Failed to get property set value as string", ex);
}
}
}
}
return DEFAULT_FILENAME + " " + dateStr;
}
/**
* Get the export directory path.
*
* @param openCase The current case.
*
* @return The export directory path.
*/
private static String getExportDirectory(Case openCase) {
String caseExportPath = openCase.getExportDirectory();
if (userDefinedExportPath == null) {
return caseExportPath;
}
File file = new File(userDefinedExportPath);
if (file.exists() == false || file.isDirectory() == false) {
return caseExportPath;
}
return userDefinedExportPath;
}
/**
* Update the default export directory. If the directory path matches the
* case export directory, then the directory used will always match the
* export directory of any given case. Otherwise, the path last used will be
* saved.
*
* @param exportPath The export path.
* @param openCase The current case.
*/
private static void updateExportDirectory(String exportPath, Case openCase) {
if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
userDefinedExportPath = null;
} else {
userDefinedExportPath = exportPath;
}
}
/**
* Thread that does the actual extraction work
*/
private static class CSVWriter extends SwingWorker<Object, Void> {
private static final Logger logger = Logger.getLogger(CSVWriter.class.getName());
private ProgressHandle progress;
private final Collection<? extends Node> nodesToExport;
private final File outputFile;
/**
* Create an instance of the CSVWriter.
*
* @param extractionTasks List of file extraction tasks.
*/
CSVWriter(Collection<? extends Node> nodesToExport, File outputFile) {
this.nodesToExport = nodesToExport;
this.outputFile = outputFile;
}
@NbBundle.Messages({"CSVWriter.progress.extracting=Exporting to CSV file",
"CSVWriter.progress.cancelling=Cancelling"})
@Override
protected Object doInBackground() throws Exception {
if (nodesToExport.isEmpty()) {
return null;
}
// Set up progress bar.
final String displayName = Bundle.CSVWriter_progress_extracting();
progress = ProgressHandle.createHandle(displayName, new Cancellable() {
@Override
public boolean cancel() {
if (progress != null) {
progress.setDisplayName(Bundle.CSVWriter_progress_cancelling());
}
return ExportCSVAction.CSVWriter.this.cancel(true);
}
});
progress.start();
progress.switchToIndeterminate();
try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) {
// Write BOM
br.write('\ufeff');
// Write the header
List<String> headers = new ArrayList<>();
PropertySet[] sets = nodesToExport.iterator().next().getPropertySets();
for(PropertySet set : sets) {
for (Property<?> prop : set.getProperties()) {
if ( ! columnsToSkip.contains(prop.getDisplayName())) {
headers.add(prop.getDisplayName());
}
}
}
br.write(listToCSV(headers));
// Write each line
Iterator<?> nodeIterator = nodesToExport.iterator();
while (nodeIterator.hasNext()) {
if (this.isCancelled()) {
break;
}
Node node = (Node)nodeIterator.next();
List<String> values = new ArrayList<>();
sets = node.getPropertySets();
for(PropertySet set : sets) {
for (Property<?> prop : set.getProperties()) {
if ( ! columnsToSkip.contains(prop.getDisplayName())) {
values.add(escapeQuotes(prop.getValue().toString()));
}
}
}
br.write(listToCSV(values));
}
}
return null;
}
/**
* Escape any quotes in the string
*
* @param original
*
* @return the string with quotes escaped
*/
private String escapeQuotes(String original) {
return original.replaceAll("\"", "\\\\\"");
}
/**
* Convert list of values to a comma separated string.
*
* @param values Values to convert
*
* @return values as CSV
*/
private String listToCSV(List<String> values) {
return "\"" + String.join("\",\"", values) + "\"\n";
}
@NbBundle.Messages({"CSVWriter.done.notifyMsg.error=Error exporting to CSV file",
"# {0} - Output file",
"CSVWriter.done.notifyMsg.success=Wrote to {0}"})
@Override
protected void done() {
boolean msgDisplayed = false;
try {
super.get();
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_error());
msgDisplayed = true;
} catch (java.util.concurrent.CancellationException ex) {
// catch and ignore if we were cancelled
} finally {
progress.finish();
if (!this.isCancelled() && !msgDisplayed) {
MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_success(outputFile));
}
}
}
}
}

View File

@ -22,6 +22,8 @@ import java.awt.EventQueue;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -278,6 +280,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
//enable the new button
FilesSetDefsPanel.this.newSetButton.setEnabled(canBeEnabled);
FilesSetDefsPanel.this.importSetButton.setEnabled(canBeEnabled);
// Get the selected interesting files set and populate the set
// components.
FilesSet selectedSet = FilesSetDefsPanel.this.setsList.getSelectedValue();
@ -294,14 +297,26 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
FilesSetDefsPanel.this.copySetButton.setEnabled(canBeEnabled);
FilesSetDefsPanel.this.exportSetButton.setEnabled(true);
// Populate the rule definitions list, sorted by name.
TreeMap<String, FilesSet.Rule> rules = new TreeMap<>(selectedSet.getRules());
rules.values().forEach((rule) -> {
List<FilesSet.Rule> rules = new ArrayList<>(selectedSet.getRules().values());
Collections.sort(rules, new Comparator<FilesSet.Rule>() {
@Override
public int compare(FilesSet.Rule rule1, FilesSet.Rule rule2) {
return rule1.toString().compareTo(rule2.toString());
}
});
rules.forEach((rule) -> {
FilesSetDefsPanel.this.rulesListModel.addElement(rule);
});
// Select the first rule by default.
if (!FilesSetDefsPanel.this.rulesListModel.isEmpty()) {
FilesSetDefsPanel.this.rulesList.setSelectedIndex(0);
}
} else {
// Disable the edit, delete, copy, and export buttons
FilesSetDefsPanel.this.editSetButton.setEnabled(false);
FilesSetDefsPanel.this.deleteSetButton.setEnabled(false);
FilesSetDefsPanel.this.copySetButton.setEnabled(false);
FilesSetDefsPanel.this.exportSetButton.setEnabled(false);
}
}

View File

@ -251,10 +251,10 @@ final class HexView extends JPanel {
this.setHighlight(startByte, endByte);
if (startByte != endByte) {
/**
* @param 1 Start
* @param 2 End
* @param 3 Len
/*
* param 1 Start
* param 2 End
* param 3 Len
*/
int length = endByte - startByte;
String text = Bundle.HexView_statusTemplate_nonZeroLength(
@ -266,8 +266,8 @@ final class HexView extends JPanel {
String.format("0x%1$x", length));
statusLabel.setText(text);
} else {
/**
* @param 1 Start
/*
* param 1 Start
*/
String text = Bundle.HexView_statusTemplate_zeroLength(startByte, String.format("0x%1$x", startByte));
statusLabel.setText(text);

View File

@ -58,10 +58,10 @@ public final class RejTreeKeyView extends RejTreeNodeView {
public RejTreeKeyView(RejTreeKeyNode node) {
super(new BorderLayout());
/**
* @param 1 Name
* @param 2 Number of subkeys
* @param 3 Number of values
/*
* param 1 Name
* param 2 Number of subkeys
* param 3 Number of values
*/
String metadataTemplate = "<html><i>"
+ Bundle.RejTreeKeyView_template_name()

View File

@ -51,9 +51,9 @@ public final class RejTreeValueView extends RejTreeNodeView {
"RejTreeValueView.valueBorder.title=Value",})
public RejTreeValueView(RejTreeValueNode node) {
super(new BorderLayout());
/**
* @param 1 Name
* @param 2 Type
/*
* param 1 Name
* param 2 Type
*/
String metadataTemplate = "<html><i>"
+ Bundle.RejTreeValueView_template_name()
@ -63,8 +63,8 @@ public final class RejTreeValueView extends RejTreeNodeView {
String valueName;
String valueType;
/**
* @param 1 Value
/*
* param 1 Value
*/
String valueTemplate = "<html>%1$s</html>";
try {

View File

@ -24,6 +24,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -63,9 +64,12 @@ class FileReportText implements FileReportModule {
public void startReport(String baseReportDir) {
this.reportPath = baseReportDir + FILE_NAME;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath)));
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.reportPath), StandardCharsets.UTF_8));
out.write('\ufeff');
} catch (FileNotFoundException ex) {
logger.log(Level.WARNING, "Failed to create report text file", ex); //NON-NLS
} catch (IOException ex) {
logger.log(Level.WARNING, "Failed to write BOM to report text file", ex); //NON-NLS
}
}

View File

@ -120,4 +120,13 @@ public final class TextTranslationService {
public boolean hasProvider() {
return selectedTranslator.isPresent();
}
/**
* Returns the hard limit for translation request sizes.
*
* @return
*/
public int getMaxPayloadSize() {
return selectedTranslator.get().getMaxPayloadSize();
}
}

View File

@ -56,4 +56,11 @@ public interface TextTranslator {
* Save the settings as they have been modified in the component.
*/
void saveSettings();
/**
* Returns the hard limit for translation request sizes.
*
* @return
*/
int getMaxPayloadSize();
}

View File

@ -0,0 +1,179 @@
/*
* 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.texttranslation.translators;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.awt.Component;
import java.io.IOException;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.texttranslation.TextTranslator;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
/**
* Translates text by making HTTP requests to Bing Translator. This requires a
* valid subscription key for a Microsoft Azure account.
*/
@ServiceProvider(service = TextTranslator.class)
public class BingTranslator implements TextTranslator {
//The target language follows the to= in the string below. You can include multiple target
//languages separated by commas. A full list of supported languages is here:
//https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support
private static final String BASE_URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=";
private static final int MAX_STRING_LENGTH = 5000;
private final BingTranslatorSettingsPanel settingsPanel;
private final BingTranslatorSettings settings = new BingTranslatorSettings();
// This sends messages to Microsoft.
private final OkHttpClient CLIENT = new OkHttpClient();
/**
* Create a Bing Translator
*/
public BingTranslator() {
settingsPanel = new BingTranslatorSettingsPanel(settings.getAuthenticationKey(), settings.getTargetLanguageCode());
}
/**
* Get the tranlationurl for the specified language code
*
*
*
* @param languageCode language code for language to translate to
*
* @return a string representation of the url to request translation from
*/
static String getTranlatorUrl(String languageCode) {
return BASE_URL + languageCode;
}
/**
* Converts an input text to the JSON format required by Bing Translator,
* posts it to Microsoft, and returns the JSON text response.
*
* @param string The input text to be translated.
*
* @return The translation response as a JSON string
*
* @throws IOException if the request could not be executed due to
* cancellation, a connectivity problem or timeout.
*/
public String postTranslationRequest(String string) throws IOException {
MediaType mediaType = MediaType.parse("application/json");
JsonArray jsonArray = new JsonArray();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("Text", string);
jsonArray.add(jsonObject);
String bodyString = jsonArray.toString();
RequestBody body = RequestBody.create(mediaType,
bodyString);
Request request = new Request.Builder()
.url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body)
.addHeader("Ocp-Apim-Subscription-Key", settings.getAuthenticationKey())
.addHeader("Content-type", "application/json").build();
Response response = CLIENT.newCall(request).execute();
return response.body().string();
}
@Override
public String translate(String string) throws TranslationException {
if (settings.getAuthenticationKey() == null || settings.getAuthenticationKey().isEmpty()) {
throw new TranslationException("Bing Translator has not been configured, authentication key needs to be specified");
}
String toTranslate = string.trim();
//Translates some text into English, without specifying the source langauge.
//Google Translate required us to replace (\r\n|\n) with <br />
//but Bing Translator doesn not have that requirement.
//The free account has a maximum file size. If you have a paid account,
//you probably still want to limit file size to prevent accidentally
//translating very large documents.
if (toTranslate.length() > MAX_STRING_LENGTH) {
toTranslate = toTranslate.substring(0, MAX_STRING_LENGTH);
}
try {
String response = postTranslationRequest(toTranslate);
return parseJSONResponse(response);
} catch (IOException | TranslationException ex) {
throw new TranslationException("Exception while attempting to translate using BingTranslator", ex);
}
}
@Messages({"BingTranslator.name.text=Bing Translator"})
@Override
public String getName() {
return Bundle.BingTranslator_name_text();
}
@Override
public Component getComponent() {
return settingsPanel;
}
@Override
public void saveSettings() {
settings.setAuthenticationKey(settingsPanel.getAuthenticationKey());
settings.setTargetLanguageCode(settingsPanel.getTargetLanguageCode());
settings.saveSettings();
}
/**
* Parse the response to get the translated text
*
* @param json_text the json which was received as a response to a
* translation request
*
* @return the translated text
*
* @throws TranslationException
*/
private String parseJSONResponse(String json_text) throws TranslationException {
/*
* Here is an example of the text we get from Bing when input is "gato",
* the Spanish word for cat: [ { "detectedLanguage": { "language": "es",
* "score": 1.0 }, "translations": [ { "text": "cat", "to": "en" } ] } ]
*/
JsonParser parser = new JsonParser();
try {
JsonArray responses = parser.parse(json_text).getAsJsonArray();
//As far as I know, there's always exactly one item in the array.
JsonObject response0 = responses.get(0).getAsJsonObject();
JsonArray translations = response0.getAsJsonArray("translations");
JsonObject translation0 = translations.get(0).getAsJsonObject();
return translation0.get("text").getAsString();
} catch (IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) {
throw new TranslationException("JSON text does not match Bing Translator scheme: " + e);
}
}
@Override
public int getMaxPayloadSize() {
return MAX_STRING_LENGTH;
}
}

View File

@ -0,0 +1,115 @@
/*
* Autopsy
*
* 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.texttranslation.translators;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/**
* Class to handle the settings associated with the BingTranslator
*/
public final class BingTranslatorSettings {
private static final String AUTHENTICATION_KEY = "Credentials";
private static final String BING_TRANSLATE_NAME = "BingTranslate";
private static final String DEFAULT_AUTHENTICATION = "";
private static final String DEFAULT_TARGET_LANGUAGE = "en";
private static final String TARGET_LANGUAGE_CODE_KEY = "TargetLanguageCode";
private String authenticationKey;
private String targetLanguageCode;
/**
* Construct a new BingTranslatorSettings object
*/
BingTranslatorSettings() {
loadSettings();
}
/**
* Get the Authentication key to be used for the Microsoft translation
* service
*
* @return the Authentication key for the service
*/
String getAuthenticationKey() {
return authenticationKey;
}
/**
* Set the Authentication key to be used for the Microsoft translation
* service
*
* @param authKey the Authentication key for the service
*/
void setAuthenticationKey(String authKey) {
authenticationKey = authKey;
}
/**
* Load the settings into memory from their on disk storage
*/
void loadSettings() {
if (!ModuleSettings.configExists(BING_TRANSLATE_NAME)) {
ModuleSettings.makeConfigFile(BING_TRANSLATE_NAME);
}
if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, AUTHENTICATION_KEY)) {
authenticationKey = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY);
}
if (authenticationKey == null || StringUtils.isBlank(authenticationKey)) {
authenticationKey = DEFAULT_AUTHENTICATION;
}
if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) {
targetLanguageCode = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY);
}
if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) {
targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
}
}
/**
* Get the target language code
*
* @return the code used to identify the target language
*/
String getTargetLanguageCode() {
return targetLanguageCode;
}
/**
* Set the target language code. If a blank code is specified it sets the
* default code instead.
*
* @param code the target language code to set
*/
void setTargetLanguageCode(String code) {
if (StringUtils.isBlank(code)) {
targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
} else {
targetLanguageCode = code;
}
}
/**
* Save the setting from memory to their location on disk
*/
void saveSettings() {
ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY, authenticationKey);
ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY, targetLanguageCode);
}
}

View File

@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="instructionsScrollPane" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="102" attributes="0">
<Component id="authenticationKeyLabel" min="-2" pref="100" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="authenticationKeyField" min="-2" pref="486" max="-2" attributes="0"/>
</Group>
<Component id="warningLabel" min="-2" pref="551" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="targetLanguageLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="testButton" alignment="0" min="-2" pref="79" max="-2" attributes="0"/>
</Group>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="untranslatedLabel" min="-2" pref="66" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="testUntranslatedTextField" min="-2" pref="140" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="resultLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="testResultValueLabel" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="targetLanguageComboBox" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="276" max="-2" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="instructionsScrollPane" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="authenticationKeyField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="authenticationKeyLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="targetLanguageLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="targetLanguageComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="testButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="testUntranslatedTextField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="untranslatedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="resultLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="testResultValueLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="warningLabel" min="-2" pref="18" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JTextField" name="authenticationKeyField">
<Properties>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.authenticationKeyField.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="warningLabel">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="ff" type="rgb"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.warningLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="testButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.testButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="testButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="targetLanguageLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.targetLanguageLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="targetLanguageComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="0"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="targetLanguageComboBoxSelected"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;LanguageWrapper&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JTextField" name="testUntranslatedTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="DEFUALT_TEST_STRING" type="code"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="untranslatedLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.untranslatedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="resultLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.resultLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="testResultValueLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.testResultValueLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="authenticationKeyLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.authenticationKeyLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JScrollPane" name="instructionsScrollPane">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
</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.JTextArea" name="instructionsTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="BingTranslatorSettingsPanel.instructionsTextArea.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="wrapStyleWord" type="boolean" value="true"/>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,334 @@
/*
* Autopsy
*
* 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.texttranslation.translators;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonObject;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
/**
* Settings panel for the BingTranslator
*/
public class BingTranslatorSettingsPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName());
private static final long serialVersionUID = 1L;
private static final String GET_TARGET_LANGUAGES_URL = "https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&scope=translation";
private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation"
private String targetLanguageCode = "";
/**
* Creates new form BingTranslatorSettingsPanel
*/
public BingTranslatorSettingsPanel(String authenticationKey, String code) {
initComponents();
authenticationKeyField.setText(authenticationKey);
authenticationKeyField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
firePropertyChange("SettingChanged", true, false);
}
@Override
public void removeUpdate(DocumentEvent e) {
firePropertyChange("SettingChanged", true, false);
}
@Override
public void changedUpdate(DocumentEvent e) {
firePropertyChange("SettingChanged", true, false);
}
});
populateComboBox();
selectLanguageByCode(code);
targetLanguageCode = code;
}
/**
* Populate the target language combo box with available target languages
*/
@Messages({"BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received"})
private void populateComboBox() {
Request get_request = new Request.Builder()
.url(GET_TARGET_LANGUAGES_URL).build();
try {
Response response = new OkHttpClient().newCall(get_request).execute();
JsonParser parser = new JsonParser();
String responseBody = response.body().string();
JsonElement elementBody = parser.parse(responseBody);
JsonObject asObject = elementBody.getAsJsonObject();
JsonElement translationElement = asObject.get("translation");
JsonObject responses = translationElement.getAsJsonObject();
responses.entrySet().forEach((entry) -> {
targetLanguageComboBox.addItem(new LanguageWrapper(entry.getKey(), entry.getValue().getAsJsonObject().get("name").getAsString()));
});
targetLanguageComboBox.setEnabled(true);
} catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException ex) {
logger.log(Level.SEVERE, Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure(), ex);
warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure());
targetLanguageComboBox.setEnabled(false);
}
}
/**
* Given a language code select the corresponding language in the combo box
* if it is present
*
* @param code language code such as "en" for English
*/
private void selectLanguageByCode(String code) {
for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) {
if (targetLanguageComboBox.getModel().getElementAt(i).getLanguageCode().equals(code)) {
targetLanguageComboBox.setSelectedIndex(i);
break;
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
authenticationKeyField = new javax.swing.JTextField();
warningLabel = new javax.swing.JLabel();
testButton = new javax.swing.JButton();
targetLanguageLabel = new javax.swing.JLabel();
targetLanguageComboBox = new javax.swing.JComboBox<>();
testUntranslatedTextField = new javax.swing.JTextField();
untranslatedLabel = new javax.swing.JLabel();
resultLabel = new javax.swing.JLabel();
testResultValueLabel = new javax.swing.JLabel();
authenticationKeyLabel = new javax.swing.JLabel();
instructionsScrollPane = new javax.swing.JScrollPane();
instructionsTextArea = new javax.swing.JTextArea();
authenticationKeyField.setToolTipText(org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyField.toolTipText")); // NOI18N
warningLabel.setForeground(new java.awt.Color(255, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testButton.text")); // NOI18N
testButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
testButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(targetLanguageLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.targetLanguageLabel.text")); // NOI18N
targetLanguageComboBox.setEnabled(false);
targetLanguageComboBox.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
targetLanguageComboBoxSelected(evt);
}
});
testUntranslatedTextField.setText(DEFUALT_TEST_STRING);
org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.resultLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(authenticationKeyLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyLabel.text")); // NOI18N
instructionsScrollPane.setBorder(javax.swing.BorderFactory.createEtchedBorder());
instructionsScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
instructionsTextArea.setEditable(false);
instructionsTextArea.setBackground(new java.awt.Color(240, 240, 240));
instructionsTextArea.setColumns(20);
instructionsTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
instructionsTextArea.setLineWrap(true);
instructionsTextArea.setRows(2);
instructionsTextArea.setText(org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.instructionsTextArea.text")); // NOI18N
instructionsTextArea.setWrapStyleWord(true);
instructionsScrollPane.setViewportView(instructionsTextArea);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(instructionsScrollPane)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(authenticationKeyLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(targetLanguageLabel)
.addComponent(testButton, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(untranslatedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 66, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(resultLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addComponent(targetLanguageComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(276, 276, 276)))))
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addComponent(instructionsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(authenticationKeyLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(targetLanguageLabel)
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(testButton)
.addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(untranslatedLabel)
.addComponent(resultLabel)
.addComponent(testResultValueLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
@Messages({"BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key"})
private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed
if (testTranslationSetup()) {
warningLabel.setText("");
} else {
warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_invalidKey());
}
}//GEN-LAST:event_testButtonActionPerformed
private void targetLanguageComboBoxSelected(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_targetLanguageComboBoxSelected
String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode();
if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) {
targetLanguageCode = selectedCode;
firePropertyChange("SettingChanged", true, false);
}
}//GEN-LAST:event_targetLanguageComboBoxSelected
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JTextField authenticationKeyField;
private javax.swing.JLabel authenticationKeyLabel;
private javax.swing.JScrollPane instructionsScrollPane;
private javax.swing.JTextArea instructionsTextArea;
private javax.swing.JLabel resultLabel;
private javax.swing.JComboBox<LanguageWrapper> targetLanguageComboBox;
private javax.swing.JLabel targetLanguageLabel;
private javax.swing.JButton testButton;
private javax.swing.JLabel testResultValueLabel;
private javax.swing.JTextField testUntranslatedTextField;
private javax.swing.JLabel untranslatedLabel;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
/**
* Attempts to translate the text specified in the Untranslated field using
* the settings currently specified but not necessarily saved
*
* @return true if the translation was able to be performed, false otherwise
*
*/
private boolean testTranslationSetup() {
testResultValueLabel.setText("");
MediaType mediaType = MediaType.parse("application/json");
JsonArray jsonArray = new JsonArray();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("Text", testUntranslatedTextField.getText());
jsonArray.add(jsonObject);
String bodyString = jsonArray.toString();
RequestBody body = RequestBody.create(mediaType,
bodyString);
Request request = new Request.Builder()
.url(BingTranslator.getTranlatorUrl(targetLanguageCode)).post(body)
.addHeader("Ocp-Apim-Subscription-Key", authenticationKeyField.getText())
.addHeader("Content-type", "application/json").build();
try {
Response response = new OkHttpClient().newCall(request).execute();
JsonParser parser = new JsonParser();
JsonArray responses = parser.parse(response.body().string()).getAsJsonArray();
//As far as I know, there's always exactly one item in the array.
JsonObject response0 = responses.get(0).getAsJsonObject();
JsonArray translations = response0.getAsJsonArray("translations");
JsonObject translation0 = translations.get(0).getAsJsonObject();
testResultValueLabel.setText(translation0.get("text").getAsString());
return true;
} catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) {
logger.log(Level.WARNING, "Test of Bing Translator failed due to exception", e);
return false;
}
}
/**
* Get the currently set authentication key to be used for the Microsoft
* translation service
*
* @return the authentication key specified in the textarea
*/
String getAuthenticationKey() {
return authenticationKeyField.getText();
}
/**
* Get the currently selected target language code
*
* @return the target language code of the language selected in the combobox
*/
String getTargetLanguageCode() {
return targetLanguageCode;
}
}

View File

@ -1,4 +1,17 @@
GoogleTranslatorSettingsPanel.browseButton.text=Browse
GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials:
GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path:
GoogleTranslatorSettingsPanel.warningLabel.text=
GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
BingTranslatorSettingsPanel.testButton.text=Test
BingTranslatorSettingsPanel.testResultValueLabel.text=
BingTranslatorSettingsPanel.resultLabel.text=Result:
BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the
GoogleTranslatorSettingsPanel.testButton.text=Test
GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
GoogleTranslatorSettingsPanel.resultLabel.text=Result:
GoogleTranslatorSettingsPanel.testResultValueLabel.text=
BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key:
BingTranslatorSettingsPanel.instructionsTextArea.text=You will need to provide a Microsoft Translator authentication key for your Microsoft Translator account. Instructions on how to get one are available here: https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup
GoogleTranslatorSettingsPanel.instructionsTextArea.text=You will need a JSON credentials file which contains your service account key for your Google Translate account. Information on how to create a service account key is available here: https://cloud.google.com/iam/docs/creating-managing-service-account-keys

View File

@ -1,8 +1,12 @@
BingTranslator.name.text=Bing Translator
BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key
BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received
GoogleTranslator.name.text=Google Translate
GoogleTranslatorSettingsPanel.browseButton.text=Browse
GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials:
GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path:
GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.noFileSelected=A JSON file must be selected to provide your credentials for Google Translate.
GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials
GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file.
@ -11,3 +15,16 @@ GoogleTranslatorSettingsPanel.fileChooser.confirmButton=Select
GoogleTranslatorSettingsPanel.json.description=JSON Files
GoogleTranslatorSettingsPanel.warningLabel.text=
GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
BingTranslatorSettingsPanel.testButton.text=Test
BingTranslatorSettingsPanel.testResultValueLabel.text=
BingTranslatorSettingsPanel.resultLabel.text=Result:
BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the
GoogleTranslatorSettingsPanel.testButton.text=Test
GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
GoogleTranslatorSettingsPanel.resultLabel.text=Result:
GoogleTranslatorSettingsPanel.testResultValueLabel.text=
BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key:
BingTranslatorSettingsPanel.instructionsTextArea.text=You will need to provide a Microsoft Translator authentication key for your Microsoft Translator account. Instructions on how to get one are available here: https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup
GoogleTranslatorSettingsPanel.instructionsTextArea.text=You will need a JSON credentials file which contains your service account key for your Google Translate account. Information on how to create a service account key is available here: https://cloud.google.com/iam/docs/creating-managing-service-account-keys

View File

@ -28,10 +28,14 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.coreutils.EscapeUtil;
import org.sleuthkit.autopsy.texttranslation.TextTranslator;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
@ -43,7 +47,8 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException;
public final class GoogleTranslator implements TextTranslator {
private static final Logger logger = Logger.getLogger(GoogleTranslator.class.getName());
private static final int MAX_STRING_LENGTH = 15000;
//See translate method for justification of this limit.
private static final int MAX_PAYLOAD_SIZE = 5000;
private final GoogleTranslatorSettingsPanel settingsPanel;
private final GoogleTranslatorSettings settings = new GoogleTranslatorSettings();
private Translate googleTranslate;
@ -57,26 +62,49 @@ public final class GoogleTranslator implements TextTranslator {
loadTranslator();
}
/**
* Check if google is able to be reached
*
* @return true if it can be, false otherwise
*/
private static boolean googleIsReachable() {
String host = "www.google.com";
InetAddress address;
try {
address = InetAddress.getByName(host);
return address.isReachable(1500);
} catch (UnknownHostException ex) {
logger.log(Level.WARNING, "Unable to reach google.com due to unknown host", ex);
return false;
} catch (IOException ex) {
logger.log(Level.WARNING, "Unable to reach google.com due IOException", ex);
return false;
}
}
@Override
public String translate(String string) throws TranslationException {
if (!googleIsReachable()) {
throw new TranslationException("Failure translating using GoogleTranslator: Cannot connect to Google");
}
if (googleTranslate != null) {
try {
// Translates some text into English, without specifying the source language.
// HTML files were producing lots of white space at the end
String substring = string.trim();
// WE can't currently set parameters, so we are using the default behavior of
// asuming the input is HTML. We need to replace newlines with <br> for Google to preserve them
// We can't currently set parameters, so we are using the default behavior of
// assuming the input is HTML. We need to replace newlines with <br> for Google to preserve them
substring = substring.replaceAll("(\r\n|\n)", "<br />");
// The API complains if the "Payload" is over 204800 bytes. I'm assuming that
// deals with the full request. At some point, we get different errors about too
// much text. Officially, Google says they will googleTranslate only 5k chars,
// but we have seen more than that working.
// there could be a value betwen 15k and 25k that works. I (BC) didn't test further
if (substring.length() > MAX_STRING_LENGTH) {
substring = substring.substring(0, MAX_STRING_LENGTH);
// The API complains if the "Payload" is over 204800 bytes. Google references that
//their service is optimized for 2K code points and recommends keeping the requests that size.
//There is a hard limit of 30K code points per request. There is also a time-based quota that
//we are not enforcing, which may lead to 403 errors. We are currently configured for a max of 5K
//in each request, for two reasons. 1) To be more in line with Google's recommendation. 2) To
//minimize accidental exceedence of time based quotas.
if (substring.length() > MAX_PAYLOAD_SIZE) {
substring = substring.substring(0, MAX_PAYLOAD_SIZE);
}
Translation translation
= googleTranslate.translate(substring);
@ -84,8 +112,12 @@ public final class GoogleTranslator implements TextTranslator {
// put back the newlines
translatedString = translatedString.replaceAll("<br />", "\n");
// With our current settings, Google Translate outputs HTML
// so we need to undo the escape characters.
translatedString = EscapeUtil.unEscapeHtml(translatedString);
return translatedString;
} catch (Throwable ex) {
} catch (Throwable ex) {
//Catching throwables because some of this Google Translate code throws throwables
throw new TranslationException("Failure translating using GoogleTranslator", ex);
}
@ -106,16 +138,20 @@ public final class GoogleTranslator implements TextTranslator {
}
/**
* Load the Google Cloud Translation service give the currently saved
* Load the Google Cloud Translation service given the currently saved
* settings
*/
private void loadTranslator() {
InputStream credentialStream = null;
Credentials creds = null;
try {
credentialStream = new FileInputStream(settings.getCredentialPath());
} catch (FileNotFoundException ex) {
logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex);
if (StringUtils.isBlank(settings.getCredentialPath())) {
logger.log(Level.INFO, "No credentials file has been provided for Google Translator");
} else {
try {
credentialStream = new FileInputStream(settings.getCredentialPath());
} catch (FileNotFoundException ex) {
logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex);
}
}
if (credentialStream != null) {
try {
@ -142,4 +178,9 @@ public final class GoogleTranslator implements TextTranslator {
settings.saveSettings();
loadTranslator();
}
@Override
public int getMaxPayloadSize() {
return MAX_PAYLOAD_SIZE;
}
}

View File

@ -92,12 +92,14 @@ public final class GoogleTranslatorSettings {
}
if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) {
targetLanguageCode = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY);
} else {
}
if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) {
targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
}
if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY)) {
credentialPath = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY);
} else {
}
if (credentialPath == null) {
credentialPath = DEFAULT_CREDENTIAL_PATH;
}
}

View File

@ -17,28 +17,47 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="warningLabel" min="-2" pref="551" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="instructionsScrollPane" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="credentialsLabel" max="32767" attributes="0"/>
<Component id="targetLanguageLabel" max="32767" attributes="0"/>
<Component id="warningLabel" min="-2" pref="551" max="-2" attributes="0"/>
<EmptySpace min="77" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="testButton" max="32767" attributes="0"/>
<Component id="credentialsLabel" alignment="0" max="32767" attributes="0"/>
<Component id="targetLanguageLabel" alignment="0" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="credentialsPathField" pref="443" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="browseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="credentialsPathField" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="browseButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="targetLanguageComboBox" min="-2" pref="317" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<Group type="102" attributes="0">
<Component id="targetLanguageComboBox" min="-2" pref="317" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="7" max="-2" attributes="0"/>
<Component id="untranslatedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="testUntranslatedTextField" min="-2" pref="140" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="resultLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="testResultValueLabel" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
@ -50,19 +69,29 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="instructionsScrollPane" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="credentialsLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="credentialsPathField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="browseButton" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="targetLanguageLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="targetLanguageComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="testButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="testUntranslatedTextField" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="untranslatedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="resultLabel" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="testResultValueLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="warningLabel" min="-2" pref="18" max="-2" attributes="0"/>
<EmptySpace pref="23" max="32767" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -79,6 +108,9 @@
<Properties>
<Property name="editable" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="credentialsPathFieldActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="browseButton">
<Properties>
@ -98,7 +130,7 @@
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;org.sleuthkit.autopsy.texttranslation.translators.GoogleLanguageWrapper&gt;"/>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;org.sleuthkit.autopsy.texttranslation.translators.LanguageWrapper&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="targetLanguageLabel">
@ -118,5 +150,82 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="testResultValueLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.testResultValueLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="resultLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.resultLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="untranslatedLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.untranslatedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JTextField" name="testUntranslatedTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="DEFUALT_TEST_STRING" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="testButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.testButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="testButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JScrollPane" name="instructionsScrollPane">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="horizontalScrollBarPolicy" type="int" value="31"/>
</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.JTextArea" name="instructionsTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
</Property>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties" key="GoogleTranslatorSettingsPanel.instructionsTextArea.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="wrapStyleWord" type="boolean" value="true"/>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.texttranslation.translators;
import com.google.auth.Credentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.translate.Language;
import com.google.cloud.translate.Translate;
import com.google.cloud.translate.TranslateOptions;
import com.google.cloud.translate.Translation;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.FileInputStream;
@ -44,6 +46,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(GoogleTranslatorSettingsPanel.class.getName());
private static final String JSON_EXTENSION = "json";
private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation"
private static final long serialVersionUID = 1L;
private final ItemListener listener = new ComboBoxSelectionListener();
private String targetLanguageCode = "";
@ -60,16 +63,15 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
/**
* Private method to make a temporary translation service given the current
* settings and use it to retrieve the available target languages for
* population of combobox with target language with unsaved settings.
* settings with unsaved settings.
*
* @return A list of Languages
* @return A Translate object which is the translation service
*/
@Messages({"GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file.",})
private List<Language> getListOfTargetLanguages() {
private Translate getTemporaryTranslationService() {
//This method also has the side effect of more or less validating the JSON file which was selected as it is necessary to get the list of target languages
try {
InputStream credentialStream;
@ -77,31 +79,31 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
credentialStream = new FileInputStream(credentialsPathField.getText());
} catch (FileNotFoundException ignored) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_fileNotFound());
return new ArrayList<>();
return null;
}
Credentials creds;
try {
creds = ServiceAccountCredentials.fromStream(credentialStream);
} catch (IOException ignored) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToMakeCredentials());
return new ArrayList<>();
return null;
}
if (creds == null) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToReadCredentials());
logger.log(Level.WARNING, "Credentials were not successfully made, no translations will be available from the GoogleTranslator");
return new ArrayList<>();
return null;
} else {
TranslateOptions.Builder builder = TranslateOptions.newBuilder();
builder.setCredentials(creds);
builder.setTargetLanguage(targetLanguageCode); //localize the list to the currently selected target language
warningLabel.setText(""); //clear any previous warning text
return builder.build().getService().listSupportedLanguages();
return builder.build().getService();
}
} catch (Throwable throwable) {
//Catching throwables because some of this Google Translate code throws throwables
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailureGetting());
logger.log(Level.WARNING, "Throwable caught while getting list of supported languages", throwable);
return new ArrayList<>();
return null;
}
}
@ -114,29 +116,51 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
targetLanguageComboBox.removeItemListener(listener);
try {
if (!StringUtils.isBlank(credentialsPathField.getText())) {
List<Language> listSupportedLanguages = getListOfTargetLanguages();
List<Language> listSupportedLanguages;
Translate tempService = getTemporaryTranslationService();
if (tempService != null) {
listSupportedLanguages = tempService.listSupportedLanguages();
} else {
listSupportedLanguages = new ArrayList<>();
}
targetLanguageComboBox.removeAllItems();
if (!listSupportedLanguages.isEmpty()) {
listSupportedLanguages.forEach((lang) -> {
targetLanguageComboBox.addItem(new GoogleLanguageWrapper(lang));
targetLanguageComboBox.addItem(new LanguageWrapper(lang));
});
selectLanguageByCode(targetLanguageCode);
targetLanguageComboBox.addItemListener(listener);
targetLanguageComboBox.setEnabled(true);
enableControls(true);
} else {
targetLanguageComboBox.setEnabled(false);
enableControls(false);
}
} else {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_noFileSelected());
targetLanguageComboBox.setEnabled(false);
enableControls(false);
}
} catch (Throwable throwable) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailurePopulating());
logger.log(Level.WARNING, "Throwable caught while populating list of supported languages", throwable);
targetLanguageComboBox.setEnabled(false);
enableControls(false);
}
}
/**
* Helper method to enable/disable all controls which are dependent on valid
* credentials having been provided
*
* @param enabled true to enable the controls, false to disable them
*/
private void enableControls(boolean enabled) {
targetLanguageComboBox.setEnabled(enabled);
testButton.setEnabled(enabled);
testResultValueLabel.setEnabled(enabled);
testUntranslatedTextField.setEnabled(enabled);
untranslatedLabel.setEnabled(enabled);
resultLabel.setEnabled(enabled);
}
/**
* Given a language code select the corresponding language in the combo box
* if it is present
@ -145,7 +169,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
*/
private void selectLanguageByCode(String code) {
for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) {
if (targetLanguageComboBox.getItemAt(i).getLanguage().getCode().equals(code)) {
if (targetLanguageComboBox.getItemAt(i).getLanguageCode().equals(code)) {
targetLanguageComboBox.setSelectedIndex(i);
return;
}
@ -167,10 +191,22 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
targetLanguageComboBox = new javax.swing.JComboBox<>();
targetLanguageLabel = new javax.swing.JLabel();
warningLabel = new javax.swing.JLabel();
testResultValueLabel = new javax.swing.JLabel();
resultLabel = new javax.swing.JLabel();
untranslatedLabel = new javax.swing.JLabel();
testUntranslatedTextField = new javax.swing.JTextField();
testButton = new javax.swing.JButton();
instructionsScrollPane = new javax.swing.JScrollPane();
instructionsTextArea = new javax.swing.JTextArea();
org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N
credentialsPathField.setEditable(false);
credentialsPathField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
credentialsPathFieldActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.browseButton.text")); // NOI18N
browseButton.addActionListener(new java.awt.event.ActionListener() {
@ -186,6 +222,38 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
warningLabel.setForeground(new java.awt.Color(255, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.resultLabel.text")); // NOI18N
resultLabel.setEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N
untranslatedLabel.setEnabled(false);
testUntranslatedTextField.setText(DEFUALT_TEST_STRING);
testUntranslatedTextField.setEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testButton.text")); // NOI18N
testButton.setEnabled(false);
testButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
testButtonActionPerformed(evt);
}
});
instructionsScrollPane.setBorder(javax.swing.BorderFactory.createEtchedBorder());
instructionsScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
instructionsTextArea.setEditable(false);
instructionsTextArea.setBackground(new java.awt.Color(240, 240, 240));
instructionsTextArea.setColumns(20);
instructionsTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
instructionsTextArea.setLineWrap(true);
instructionsTextArea.setRows(2);
instructionsTextArea.setText(org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.instructionsTextArea.text")); // NOI18N
instructionsTextArea.setWrapStyleWord(true);
instructionsScrollPane.setViewportView(instructionsTextArea);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -194,27 +262,44 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(instructionsScrollPane)
.addContainerGap())
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(credentialsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(targetLanguageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(testButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(credentialsLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(targetLanguageLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(credentialsPathField, javax.swing.GroupLayout.DEFAULT_SIZE, 443, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseButton)
.addGap(14, 14, 14))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(credentialsPathField)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseButton)
.addGap(14, 14, 14))
.addGroup(layout.createSequentialGroup()
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 317, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, Short.MAX_VALUE))))
.addGroup(layout.createSequentialGroup()
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 317, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, Short.MAX_VALUE))))))
.addGap(7, 7, 7)
.addComponent(untranslatedLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(resultLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(testResultValueLabel, 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()
.addContainerGap()
.addComponent(instructionsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(credentialsLabel)
.addComponent(credentialsPathField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -223,9 +308,16 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(targetLanguageLabel)
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(testButton)
.addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(untranslatedLabel)
.addComponent(resultLabel)
.addComponent(testResultValueLabel))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(23, Short.MAX_VALUE))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
@ -249,12 +341,39 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
}
}//GEN-LAST:event_browseButtonActionPerformed
@Messages({"GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials"})
private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed
testResultValueLabel.setText("");
Translate tempTranslate = getTemporaryTranslationService();
if (tempTranslate != null) {
try {
Translation translation = tempTranslate.translate(testUntranslatedTextField.getText());
testResultValueLabel.setText(translation.getTranslatedText());
warningLabel.setText("");
} catch (Exception ex) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure());
logger.log(Level.WARNING, Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure(), ex);
}
}
}//GEN-LAST:event_testButtonActionPerformed
private void credentialsPathFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_credentialsPathFieldActionPerformed
// TODO add your handling code here:
}//GEN-LAST:event_credentialsPathFieldActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
private javax.swing.JLabel credentialsLabel;
private javax.swing.JTextField credentialsPathField;
private javax.swing.JComboBox<org.sleuthkit.autopsy.texttranslation.translators.GoogleLanguageWrapper> targetLanguageComboBox;
private javax.swing.JScrollPane instructionsScrollPane;
private javax.swing.JTextArea instructionsTextArea;
private javax.swing.JLabel resultLabel;
private javax.swing.JComboBox<org.sleuthkit.autopsy.texttranslation.translators.LanguageWrapper> targetLanguageComboBox;
private javax.swing.JLabel targetLanguageLabel;
private javax.swing.JButton testButton;
private javax.swing.JLabel testResultValueLabel;
private javax.swing.JTextField testUntranslatedTextField;
private javax.swing.JLabel untranslatedLabel;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
@ -284,7 +403,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
@Override
public void itemStateChanged(java.awt.event.ItemEvent evt) {
String selectedCode = ((GoogleLanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguage().getCode();
String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode();
if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) {
targetLanguageCode = selectedCode;
populateTargetLanguageComboBox();

View File

@ -21,19 +21,33 @@ package org.sleuthkit.autopsy.texttranslation.translators;
import com.google.cloud.translate.Language;
/**
* Wrapper for the Language class
* Wrapper for Language definitions used by translators
*/
class GoogleLanguageWrapper {
class LanguageWrapper {
private final Language language;
private final String languageCode;
private final String languageDisplayName;
/**
* Create a new GoogleLanguageWrapper
* Create a new LanguageWrapper to wrap the google language object
*
* @param lang the Language object to wrap
*/
GoogleLanguageWrapper(Language lang) {
language = lang;
LanguageWrapper(Language language) {
languageCode = language.getCode();
languageDisplayName = language.getName();
}
/**
* Create a new LanguageWrapper to wrap json elements that identify a
* language for microsofts translation service
*
* @param code the code which uniquely identifies a language
* @param name the name of the language
*/
LanguageWrapper(String code, String name) {
languageCode = code;
languageDisplayName = name;
}
/**
@ -41,14 +55,14 @@ class GoogleLanguageWrapper {
*
* @return the wrapped Language
*/
Language getLanguage() {
return language;
String getLanguageCode() {
return languageCode;
}
@Override
public String toString() {
//toString overridden so that the jComboBox in the GoogleTranslatorSettingsPanel will display the name of the language
return language.getName();
//toString overridden so that the jComboBox in the TranslatorSettingsPanels will display the name of the language
return languageDisplayName;
}
}

View File

@ -1,8 +1,8 @@
OptionsCategory_Name_Machine_Translation=Machine Translation
OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings
TranslationContentPanel.ShowLabel.text=Show:
TranslationContentPanel.warningLabel2MB.text=Only the first 1MB of text will be displayed
TranslationContentPanel.ocrLabel.text=OCR:
TranslationOptionsPanel.translationServiceLabel.text=Text translator:
TranslationOptionsPanelController.moduleErr=Module Error
TranslationOptionsPanelController.moduleErr.msg=A module caused an error listening to TranslationSettingsPanelController updates. See log to determine which module. Some data could be incomplete.
TranslationOptionsPanel.translationOptionsDescription.text=Configure a 3rd party text translation service to enable text and file name translation.

View File

@ -1,6 +1,6 @@
OptionsCategory_Name_Machine_Translation=Machine Translation
OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings
TranslatedContentPanel.comboBoxOption.originalText=Original Text
TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB)
TranslatedContentPanel.comboBoxOption.translatedText=Translated Text
TranslatedContentViewer.emptyTranslation=The resulting translation was empty.
TranslatedContentViewer.errorExtractingText=Could not extract text from file.
@ -12,11 +12,11 @@ TranslatedContentViewer.noServiceProvider=Machine Translation software was not f
TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer.
TranslatedContentViewer.translatingText=Translating text, please wait...
TranslatedContentViewer.translationException=Error encountered while attempting translation.
TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated
TranslatedTextViewer.title=Translation
TranslatedTextViewer.toolTip=Displays translated file text.
TranslationContentPanel.autoDetectOCR=Autodetect language
TranslationContentPanel.ShowLabel.text=Show:
TranslationContentPanel.warningLabel2MB.text=Only the first 1MB of text will be displayed
TranslationContentPanel.ocrLabel.text=OCR:
TranslationOptionsPanel.noTextTranslators.text=No text translators exist, translation is disabled.
TranslationOptionsPanel.noTextTranslatorSelected.text=No text translator selected, translation is disabled.
@ -25,3 +25,4 @@ TranslationOptionsPanel.translationDisabled.text=Translation disabled
TranslationOptionsPanel.translationServiceLabel.text=Text translator:
TranslationOptionsPanelController.moduleErr=Module Error
TranslationOptionsPanelController.moduleErr.msg=A module caused an error listening to TranslationSettingsPanelController updates. See log to determine which module. Some data could be incomplete.
TranslationOptionsPanel.translationOptionsDescription.text=Configure a 3rd party text translation service to enable text and file name translation.

View File

@ -67,7 +67,7 @@ public final class TranslatedTextViewer implements TextViewer {
private static final boolean OCR_ENABLED = true;
private static final boolean OCR_DISABLED = false;
private static final int MAX_SIZE_1MB = 1024000;
private static final int MAX_EXTRACT_SIZE_BYTES = 25600;
private static final List<String> INSTALLED_LANGUAGE_PACKS = PlatformUtil.getOcrLanguagePacks();
private final TranslationContentPanel panel = new TranslationContentPanel();
@ -77,6 +77,9 @@ public final class TranslatedTextViewer implements TextViewer {
= new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build();
private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory);
@NbBundle.Messages({
"TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated"
})
@Override
public void setNode(final Node node) {
this.node = node;
@ -92,6 +95,9 @@ public final class TranslatedTextViewer implements TextViewer {
panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS);
}
}
int payloadMaxInKB = TextTranslationService.getInstance().getMaxPayloadSize() / 1000;
panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB));
//Force a background task.
displayDropDownListener.actionPerformed(null);
@ -296,8 +302,9 @@ public final class TranslatedTextViewer implements TextViewer {
//Correct for UTF-8
byte[] resultInUTF8Bytes = result.getBytes("UTF8");
byte[] trimTo1MB = Arrays.copyOfRange(resultInUTF8Bytes, 0, MAX_SIZE_1MB );
return new String(trimTo1MB, "UTF-8");
byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0,
Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES) );
return new String(trimToArraySize, "UTF-8");
}
/**
@ -331,7 +338,7 @@ public final class TranslatedTextViewer implements TextViewer {
//Short-circuit the read if its greater than our max
//translatable size
int bytesLeft = MAX_SIZE_1MB - bytesRead;
int bytesLeft = MAX_EXTRACT_SIZE_BYTES - bytesRead;
if (bytesLeft < read) {
textBuilder.append(cbuf, 0, bytesLeft);
@ -341,9 +348,8 @@ public final class TranslatedTextViewer implements TextViewer {
textBuilder.append(cbuf, 0, read);
bytesRead += read;
}
// The trim is on here because HTML files were observed with nearly 1MB of white space at the end
return textBuilder.toString().trim();
return textBuilder.toString();
}
/**

View File

@ -16,7 +16,7 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jPanel1" alignment="1" pref="628" max="32767" attributes="0"/>
<Component id="jPanel1" alignment="1" pref="872" max="32767" attributes="0"/>
<Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
@ -48,12 +48,12 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="warningLabel2MB" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="warningLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="422" max="32767" attributes="0"/>
<Component id="ShowLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="displayTextComboBox" min="-2" pref="128" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="displayTextComboBox" min="-2" pref="150" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="ocrLabel" min="-2" pref="26" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="ocrDropdown" min="-2" pref="180" max="-2" attributes="0"/>
@ -72,18 +72,25 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="9" pref="9" max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Group type="103" alignment="1" groupAlignment="3" attributes="0">
<Component id="displayTextComboBox" alignment="3" pref="26" max="32767" attributes="0"/>
<Component id="ShowLabel" alignment="3" max="32767" attributes="0"/>
<Component id="ocrLabel" alignment="3" max="32767" attributes="0"/>
<Component id="warningLabel2MB" alignment="3" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="9" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="warningLabel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="103" alignment="1" groupAlignment="3" attributes="0">
<Component id="displayTextComboBox" alignment="3" pref="26" max="32767" attributes="0"/>
<Component id="ShowLabel" alignment="3" max="32767" attributes="0"/>
<Component id="ocrLabel" alignment="3" max="32767" attributes="0"/>
</Group>
<Component id="ocrDropdown" alignment="1" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="7" max="-2" attributes="0"/>
</Group>
<Component id="ocrDropdown" alignment="1" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="7" max="-2" attributes="0"/>
</Group>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
@ -153,14 +160,11 @@
<Property name="enabled" type="boolean" value="false"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="warningLabel2MB">
<Component class="javax.swing.JLabel" name="warningLabel">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/images/warning16.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties" key="TranslationContentPanel.warningLabel2MB.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>

View File

@ -87,6 +87,10 @@ public class TranslationContentPanel extends javax.swing.JPanel {
dropDown.removeActionListener(listener);
}
}
void setWarningLabelMsg(String msg) {
warningLabel.setText(msg);
}
@NbBundle.Messages({"TranslationContentPanel.autoDetectOCR=Autodetect language"})
final void reset() {
@ -246,7 +250,7 @@ public class TranslationContentPanel extends javax.swing.JPanel {
/**
* Selection choices to be displayed in the combobox dropdown.
*/
@Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text",
@Messages({"TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB)",
"TranslatedContentPanel.comboBoxOption.translatedText=Translated Text"})
static enum DisplayDropdownOptions {
ORIGINAL_TEXT(Bundle.TranslatedContentPanel_comboBoxOption_originalText()),
@ -280,7 +284,7 @@ public class TranslationContentPanel extends javax.swing.JPanel {
displayTextComboBox = new javax.swing.JComboBox<>();
ocrDropdown = new javax.swing.JComboBox<>();
ocrLabel = new javax.swing.JLabel();
warningLabel2MB = new javax.swing.JLabel();
warningLabel = new javax.swing.JLabel();
jScrollPane1 = new javax.swing.JScrollPane();
displayTextArea = new javax.swing.JTextArea();
@ -302,8 +306,7 @@ public class TranslationContentPanel extends javax.swing.JPanel {
org.openide.awt.Mnemonics.setLocalizedText(ocrLabel, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.ocrLabel.text")); // NOI18N
ocrLabel.setEnabled(false);
warningLabel2MB.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(warningLabel2MB, org.openide.util.NbBundle.getMessage(TranslationContentPanel.class, "TranslationContentPanel.warningLabel2MB.text")); // NOI18N
warningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
@ -311,12 +314,12 @@ public class TranslationContentPanel extends javax.swing.JPanel {
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addComponent(warningLabel2MB)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(warningLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 422, Short.MAX_VALUE)
.addComponent(ShowLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(displayTextComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 128, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(displayTextComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 150, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(ocrLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(ocrDropdown, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE)
@ -331,16 +334,20 @@ public class TranslationContentPanel extends javax.swing.JPanel {
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createSequentialGroup()
.addGap(9, 9, 9)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(displayTextComboBox, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE)
.addComponent(ShowLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(warningLabel2MB))
.addComponent(ocrDropdown))
.addGap(7, 7, 7))
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(warningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(displayTextComboBox, javax.swing.GroupLayout.DEFAULT_SIZE, 26, Short.MAX_VALUE)
.addComponent(ShowLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(ocrLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(ocrDropdown))
.addGap(7, 7, 7))))
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
@ -362,7 +369,7 @@ public class TranslationContentPanel extends javax.swing.JPanel {
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 628, Short.MAX_VALUE)
.addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 872, Short.MAX_VALUE)
.addComponent(jScrollPane1)
);
layout.setVerticalGroup(
@ -384,6 +391,6 @@ public class TranslationContentPanel extends javax.swing.JPanel {
private javax.swing.JSeparator jSeparator2;
private javax.swing.JComboBox<String> ocrDropdown;
private javax.swing.JLabel ocrLabel;
private javax.swing.JLabel warningLabel2MB;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
}

View File

@ -19,13 +19,14 @@
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="translationServicePanel" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Component id="translationServiceLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
<Component id="translatorComboBox" min="-2" pref="214" max="-2" attributes="0"/>
<EmptySpace min="0" pref="162" max="32767" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Component id="translationServicePanel" alignment="1" max="32767" attributes="0"/>
<Component id="translationOptionsDescription" alignment="0" pref="462" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
@ -34,14 +35,16 @@
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="translationOptionsDescription" min="-2" pref="15" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="translatorComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="translationServiceLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="translationServicePanel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -66,5 +69,12 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
<Component class="javax.swing.JLabel" name="translationOptionsDescription">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties" key="TranslationOptionsPanel.translationOptionsDescription.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -145,6 +145,7 @@ public class TranslationOptionsPanel extends javax.swing.JPanel {
translatorComboBox = new javax.swing.JComboBox<>();
translationServiceLabel = new javax.swing.JLabel();
translationServicePanel = new javax.swing.JPanel();
translationOptionsDescription = new javax.swing.JLabel();
translatorComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
@ -156,6 +157,8 @@ public class TranslationOptionsPanel extends javax.swing.JPanel {
translationServicePanel.setLayout(new java.awt.BorderLayout());
org.openide.awt.Mnemonics.setLocalizedText(translationOptionsDescription, org.openide.util.NbBundle.getMessage(TranslationOptionsPanel.class, "TranslationOptionsPanel.translationOptionsDescription.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -163,18 +166,21 @@ public class TranslationOptionsPanel extends javax.swing.JPanel {
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(translationServicePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(translationServiceLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGap(10, 10, 10)
.addComponent(translatorComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 214, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 162, Short.MAX_VALUE))
.addComponent(translationServicePanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(translationOptionsDescription, javax.swing.GroupLayout.DEFAULT_SIZE, 462, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(translationOptionsDescription, javax.swing.GroupLayout.PREFERRED_SIZE, 15, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(translatorComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(translationServiceLabel))
@ -190,6 +196,7 @@ public class TranslationOptionsPanel extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel translationOptionsDescription;
private javax.swing.JLabel translationServiceLabel;
private javax.swing.JPanel translationServicePanel;
private javax.swing.JComboBox<String> translatorComboBox;

View File

@ -0,0 +1,92 @@
/*
* 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.texttranslation.translators;
//import java.io.IOException;
//import org.junit.After;
//import org.junit.AfterClass;
//import org.junit.Before;
//import org.junit.BeforeClass;
import org.junit.Test;
//import static org.junit.Assert.*;
/**
* Tests for the BingTranslator translation service, these tests have been
* commented out because they require credentials to perform
*/
public class BingTranslatorTest {
@Test
public void testTranslate() {
// BingTranslator translator = new BingTranslator();
// String input = "gato";
// String expectedTranslation = "cat";
// runTest(translator, input, expectedTranslation);
}
// @Test
// public void testQuickStartSentence() throws Exception {
// BingTranslator translator = new BingTranslator();
// String input = "Willkommen bei Microsoft Translator. Raten Sie mal, wie viele Sprachen ich spreche.";
// String expectedTranslation = "Welcome to Microsoft Translator. Guess how many languages I speak.";
// runTest(translator, input, expectedTranslation);
// }
//
// @Test
// public void testCharacterEscapes() throws Exception {
// BingTranslator translator = new BingTranslator();
// String input = "\"gato\"";;
// String expectedTranslation = "Cat";
// runTest(translator, input, expectedTranslation);
// }
//
// @Test
// public void testLineBreaks() throws Exception {
// BingTranslator translator = new BingTranslator();
// String input = "gato\nperro";;
// String expectedTranslation = "cat\nDog";
// runTest(translator, input, expectedTranslation);
// }
//
// /**
// * Test whether translator throws an error. This should not be part of our
// * regular testing, because we are limited to only 2MB of free translations
// * ever.
// * @param translator A BingTranslator
// * @param input Text to translate
// * @param expectedTranslation Not used unless you uncomment those lines.
// */
// public void runTest(BingTranslator translator, String input, String expectedTranslation) {
// String translation;
// try {
// translation = translator.translate(input);
// }
// catch (Throwable e) {
// fail("Bing translation produced an exception: " + e.getMessage());
// return;
// };
//
// /*
// //It's unrealistic to expect the same answer every time, but sometimes
// //it's helpful to have this in your debug process.
// System.out.println(translation);
// assertEquals(expectedTranslation, translation);
// */
// }
}

View File

@ -698,7 +698,7 @@ public final class ImageGalleryController {
//grab files with supported mime-types
+ MIMETYPE_CLAUSE //NON-NLS
//grab files with image or video mime-types even if we don't officially support them
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )" //NON-NLS
+ " ORDER BY parent_path ";
}

View File

@ -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");

View File

@ -32,6 +32,7 @@ import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.ProxyLookup;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExportCSVAction;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
@ -163,6 +164,7 @@ class AdHocSearchFilterNode extends FilterNode {
actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal()));
actionsList.add(null);
actionsList.add(ExtractAction.getInstance());
actionsList.add(ExportCSVAction.getInstance());
actionsList.add(null); // creates a menu separator
actionsList.add(AddContentTagAction.getInstance());

View File

@ -36,7 +36,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found
KeywordSearchResultFactory.query.exception.msg=Could not perform the query
OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Name=KeywordSearch
OptionsCategory_Name_KeywordSearchOptions=Keyword Search
OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search

Some files were not shown because too many files have changed in this diff Show More