mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge remote-tracking branch 'upstream/develop' into improved-case-deletion
This commit is contained in:
commit
1bcca8ec70
@ -2028,6 +2028,43 @@ public class Case {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty portable case from the current case
|
||||
*
|
||||
* @param caseName Case name
|
||||
* @param portableCaseFolder Case folder - must not exist
|
||||
*
|
||||
* @return The portable case database
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
|
||||
|
||||
if (portableCaseFolder.exists()) {
|
||||
throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
|
||||
}
|
||||
if (! portableCaseFolder.mkdirs()) {
|
||||
throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
|
||||
}
|
||||
|
||||
CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
|
||||
getExaminerPhone(), getExaminerEmail(), getCaseNotes());
|
||||
try {
|
||||
CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
|
||||
caseName, details, metadata);
|
||||
portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
|
||||
} catch (CaseMetadataException ex) {
|
||||
throw new TskCoreException("Error creating case metadata", ex);
|
||||
}
|
||||
|
||||
// Create the Sleuthkit case
|
||||
SleuthkitCase portableSleuthkitCase;
|
||||
String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
|
||||
portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
|
||||
|
||||
return portableSleuthkitCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks current thread for an interrupt. Usage: checking for user
|
||||
* cancellation of a case creation/opening operation, as reflected in the
|
||||
|
@ -115,7 +115,7 @@ public final class CaseDetails implements Serializable {
|
||||
/**
|
||||
* Get the case notes
|
||||
*
|
||||
* @return notes - the note asssociated with the case
|
||||
* @return notes - the note associated with the case
|
||||
*/
|
||||
public String getCaseNotes() {
|
||||
return caseNotes;
|
||||
|
@ -95,13 +95,19 @@ public final class CaseMetadata {
|
||||
private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
|
||||
private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
|
||||
|
||||
/*
|
||||
* Fields from schema version 5
|
||||
*/
|
||||
private static final String SCHEMA_VERSION_FIVE = "5.0";
|
||||
private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
|
||||
|
||||
/*
|
||||
* Unread fields, regenerated on save.
|
||||
*/
|
||||
private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
|
||||
private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
|
||||
|
||||
private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FOUR;
|
||||
private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FIVE;
|
||||
|
||||
private final Path metadataFilePath;
|
||||
private Case.CaseType caseType;
|
||||
@ -112,6 +118,7 @@ public final class CaseMetadata {
|
||||
private String textIndexName; // Legacy
|
||||
private String createdDate;
|
||||
private String createdByVersion;
|
||||
private CaseMetadata originalMetadata = null; // For portable cases
|
||||
|
||||
/**
|
||||
* Gets the file extension used for case metadata files.
|
||||
@ -136,15 +143,27 @@ public final class CaseMetadata {
|
||||
* persisted to the case metadata file until writeFile or a setX method is
|
||||
* called.
|
||||
*
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDisplayName The display name of the case, can be changed by a
|
||||
* user.
|
||||
* @param caseNumber The case number.
|
||||
* @param examiner The name of the case examiner.
|
||||
* @param caseDetails The details for the case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
|
||||
this(caseType, caseDirectory, caseName, caseDetails, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CaseMetadata object for a new case. The metadata is not
|
||||
* persisted to the case metadata file until writeFile or a setX method is
|
||||
* called.
|
||||
*
|
||||
* @param caseType The type of case.
|
||||
* @param caseDirectory The case directory.
|
||||
* @param caseName The immutable name of the case.
|
||||
* @param caseDetails The details for the case
|
||||
* @param originalMetadata The metadata object from the original case
|
||||
*/
|
||||
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
|
||||
metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
|
||||
this.caseType = caseType;
|
||||
this.caseName = caseName;
|
||||
@ -154,6 +173,7 @@ public final class CaseMetadata {
|
||||
textIndexName = "";
|
||||
createdByVersion = Version.getVersion();
|
||||
createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
|
||||
this.originalMetadata = originalMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -417,17 +437,42 @@ public final class CaseMetadata {
|
||||
/*
|
||||
* Create the children of the case element.
|
||||
*/
|
||||
createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
|
||||
createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetails.getCaseDisplayName());
|
||||
createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetails.getCaseNumber());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetails.getExaminerName());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetails.getExaminerPhone());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetails.getExaminerEmail());
|
||||
createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetails.getCaseNotes());
|
||||
createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
|
||||
createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath);
|
||||
createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName);
|
||||
createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
|
||||
createCaseElements(doc, caseElement, this);
|
||||
|
||||
/*
|
||||
* Add original case element
|
||||
*/
|
||||
Element originalCaseElement = doc.createElement(ORIGINAL_CASE_ELEMENT_NAME);
|
||||
rootElement.appendChild(originalCaseElement);
|
||||
if (originalMetadata != null) {
|
||||
createChildElement(doc, originalCaseElement, CREATED_DATE_ELEMENT_NAME, originalMetadata.createdDate);
|
||||
Element originalCaseDetailsElement = doc.createElement(CASE_ELEMENT_NAME);
|
||||
originalCaseElement.appendChild(originalCaseDetailsElement);
|
||||
createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the case element children for the given metadata object
|
||||
*
|
||||
* @param doc The document.
|
||||
* @param caseElement The case element parent
|
||||
* @param metadataToWrite The CaseMetadata object to read from
|
||||
*/
|
||||
private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
|
||||
CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
|
||||
createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, metadataToWrite.caseName);
|
||||
createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetailsToWrite.getCaseDisplayName());
|
||||
createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetailsToWrite.getCaseNumber());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetailsToWrite.getExaminerName());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetailsToWrite.getExaminerPhone());
|
||||
createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetailsToWrite.getExaminerEmail());
|
||||
createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetailsToWrite.getCaseNotes());
|
||||
createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, metadataToWrite.caseType.toString());
|
||||
createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, metadataToWrite.caseDatabasePath);
|
||||
createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, metadataToWrite.caseDatabaseName);
|
||||
createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, metadataToWrite.textIndexName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,7 +550,9 @@ public final class CaseMetadata {
|
||||
examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
|
||||
caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
|
||||
}
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
|
||||
|
||||
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail,
|
||||
caseNotes);
|
||||
this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
|
||||
if (null == this.caseType) {
|
||||
throw new CaseMetadataException("Case metadata file corrupted");
|
||||
|
@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.FileSystem;
|
||||
import org.sleuthkit.datamodel.LocalDirectory;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.VolumeSystem;
|
||||
|
||||
@ -88,6 +89,13 @@ class ContentChildren extends AbstractContentChildren<Content> {
|
||||
} else {
|
||||
children.add(c);
|
||||
}
|
||||
} else if (c instanceof LocalDirectory) {
|
||||
LocalDirectory localDir = (LocalDirectory)c;
|
||||
if (localDir.isRoot()) {
|
||||
children.addAll(getDisplayChildren(localDir));
|
||||
} else {
|
||||
children.add(c);
|
||||
}
|
||||
} else {
|
||||
children.add(c);
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentVisitor;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
@ -242,6 +243,17 @@ public class ViewContextAction extends AbstractAction {
|
||||
*/
|
||||
DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal();
|
||||
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content));
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
BlackboardArtifact artifact = ((BlackboardArtifact) content);
|
||||
long associatedId = artifact.getObjectID();
|
||||
try {
|
||||
Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId);
|
||||
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TreeView treeView = treeViewTopComponent.getTree();
|
||||
treeView.expandNode(parentTreeViewNode);
|
||||
if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) {
|
||||
|
@ -109,9 +109,9 @@ final class DataSourceIngestPipeline {
|
||||
this.job.updateDataSourceIngestProgressBarDisplayName(displayName);
|
||||
this.job.switchDataSourceIngestProgressBarToIndeterminate();
|
||||
DataSourceIngestPipeline.ingestManager.setIngestTaskProgress(task, module.getDisplayName());
|
||||
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) starting", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
|
||||
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) starting", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getId()}); //NON-NLS
|
||||
module.process(dataSource, new DataSourceIngestModuleProgress(this.job));
|
||||
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) finished", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
|
||||
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) finished", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getId()}); //NON-NLS
|
||||
} catch (Throwable ex) { // Catch-all exception firewall
|
||||
errors.add(new IngestModuleError(module.getDisplayName(), ex));
|
||||
String msg = ex.getMessage();
|
||||
|
@ -78,6 +78,12 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
|
||||
|
||||
try {
|
||||
if (dataSource instanceof Image) {
|
||||
|
||||
if (((Image) dataSource).getPaths().length == 0) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to process data source '%s' - image has no paths", dataSource.getName()));
|
||||
return IngestModule.ProcessResult.ERROR;
|
||||
}
|
||||
|
||||
List<VolumeSystem> volumeSystems = ((Image) dataSource).getVolumeSystems();
|
||||
for (VolumeSystem volumeSystem : volumeSystems) {
|
||||
for (Volume volume : volumeSystem.getVolumes()) {
|
||||
|
@ -252,3 +252,9 @@ ReportHTMLConfigurationPanel.headerTextField.text=
|
||||
ReportHTMLConfigurationPanel.footerTextField.text=
|
||||
ReportHTMLConfigurationPanel.headerLabel.text=Header:
|
||||
ReportHTMLConfigurationPanel.footerLabel.text=Footer:
|
||||
CreatePortableCasePanel.selectAllButton.text=Select All
|
||||
CreatePortableCasePanel.deselectAllButton.text=Deselect All
|
||||
CreatePortableCasePanel.outputFolderTextField.text=jTextField1
|
||||
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
|
||||
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
|
||||
CreatePortableCasePanel.jLabel2.text=Select output folder:
|
||||
|
@ -0,0 +1,476 @@
|
||||
/*
|
||||
* 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.report;
|
||||
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import javax.swing.JPanel;
|
||||
import java.util.logging.Level;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.FileUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.utils.FileTypeUtils.FileTypeCategory;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.FileSystem;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.LocalFilesDataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.Volume;
|
||||
import org.sleuthkit.datamodel.VolumeSystem;
|
||||
|
||||
/**
|
||||
* Creates a portable case from tagged files
|
||||
*/
|
||||
@ServiceProvider(service = GeneralReportModule.class)
|
||||
public class CreatePortableCaseModule implements GeneralReportModule {
|
||||
private static final Logger logger = Logger.getLogger(CreatePortableCaseModule.class.getName());
|
||||
private static final String FILE_FOLDER_NAME = "PortableCaseFiles";
|
||||
private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other";
|
||||
private CreatePortableCasePanel configPanel;
|
||||
|
||||
// These are the types for the exported file subfolders
|
||||
private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
|
||||
FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO);
|
||||
|
||||
private Case currentCase = null;
|
||||
private SleuthkitCase skCase = null;
|
||||
private File caseFolder = null;
|
||||
private File copiedFilesFolder = null;
|
||||
|
||||
// Maps old object ID from current case to new object in portable case
|
||||
private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
|
||||
|
||||
// Maps new object ID to the new object
|
||||
private final Map<Long, Content> newIdToContent = new HashMap<>();
|
||||
|
||||
// Maps old TagName to new TagName
|
||||
private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
|
||||
|
||||
public CreatePortableCaseModule() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"CreatePortableCaseModule.getName.name=Portable Case"
|
||||
})
|
||||
@Override
|
||||
public String getName() {
|
||||
return Bundle.CreatePortableCaseModule_getName_name();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"CreatePortableCaseModule.getDescription.description=Copies selected tagged items to a new single-user case that will work anywhere"
|
||||
})
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return Bundle.CreatePortableCaseModule_getDescription_description();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelativeFilePath() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to avoid code duplication.
|
||||
* Assumes that if an exception is supplied then the error is SEVERE. Otherwise
|
||||
* it is logged as a WARNING.
|
||||
*
|
||||
* @param logWarning Warning to write to the log
|
||||
* @param dialogWarning Warning to write to a pop-up window
|
||||
* @param ex The exception (can be null)
|
||||
* @param progressPanel The report progress panel
|
||||
*/
|
||||
private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
|
||||
if (ex == null) {
|
||||
logger.log(Level.WARNING, logWarning);
|
||||
} else {
|
||||
logger.log(Level.SEVERE, logWarning, ex);
|
||||
}
|
||||
MessageNotifyUtil.Message.error(dialogWarning);
|
||||
progressPanel.setIndeterminate(false);
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"CreatePortableCaseModule.generateReport.verifying=Verifying selected parameters...",
|
||||
"CreatePortableCaseModule.generateReport.creatingCase=Creating portable case database...",
|
||||
"CreatePortableCaseModule.generateReport.copyingTags=Copying tags...",
|
||||
"# {0} - tag name",
|
||||
"CreatePortableCaseModule.generateReport.copyingFiles=Copying files tagged as {0}...",
|
||||
"# {0} - output folder",
|
||||
"CreatePortableCaseModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
|
||||
"# {0} - output folder",
|
||||
"CreatePortableCaseModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
|
||||
"CreatePortableCaseModule.generateReport.noTagsSelected=No tags selected for export.",
|
||||
"CreatePortableCaseModule.generateReport.caseClosed=Current case has been closed",
|
||||
"CreatePortableCaseModule.generateReport.errorCopyingTags=Error copying tags",
|
||||
"CreatePortableCaseModule.generateReport.errorCopyingFiles=Error copying tagged files"
|
||||
})
|
||||
@Override
|
||||
public void generateReport(String reportPath, ReportProgressPanel progressPanel) {
|
||||
progressPanel.setIndeterminate(true);
|
||||
progressPanel.start();
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_verifying());
|
||||
|
||||
// Clear out any old values
|
||||
cleanup();
|
||||
|
||||
// Validate the input parameters
|
||||
File outputDir = new File(configPanel.getOutputFolder());
|
||||
if (! outputDir.exists()) {
|
||||
handleError("Output folder " + outputDir.toString() + " does not exist",
|
||||
Bundle.CreatePortableCaseModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
if (! outputDir.isDirectory()) {
|
||||
handleError("Output folder " + outputDir.toString() + " is not a folder",
|
||||
Bundle.CreatePortableCaseModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
List<TagName> tagNames = configPanel.getSelectedTagNames();
|
||||
if (tagNames.isEmpty()) {
|
||||
handleError("No tags selected for export",
|
||||
Bundle.CreatePortableCaseModule_generateReport_noTagsSelected(), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the current case object
|
||||
try {
|
||||
currentCase = Case.getCurrentCaseThrows();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
handleError("Current case has been closed",
|
||||
Bundle.CreatePortableCaseModule_generateReport_caseClosed(), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create the case.
|
||||
// skCase and caseFolder will be set here.
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_creatingCase());
|
||||
createCase(outputDir, progressPanel);
|
||||
if (skCase == null) {
|
||||
// The error has already been handled
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the selected tags
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_copyingTags());
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
TagName newTagName = skCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
|
||||
oldTagNameToNewTagName.put(tagName, newTagName);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
handleError("Error copying tags", Bundle.CreatePortableCaseModule_generateReport_errorCopyingTags(), ex, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the tagged files
|
||||
try {
|
||||
for(TagName tagName:tagNames) {
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
return;
|
||||
}
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_copyingFiles(tagName.getDisplayName()));
|
||||
addFilesToPortableCase(tagName, progressPanel);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
handleError("Error copying tagged files", Bundle.CreatePortableCaseModule_generateReport_errorCopyingFiles(), ex, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the case connections and clear out the maps
|
||||
cleanup();
|
||||
|
||||
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the case directory and case database.
|
||||
* skCase will be set if this completes without error.
|
||||
*
|
||||
* @param outputDir The parent for the case folder
|
||||
* @param progressPanel
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - case folder",
|
||||
"CreatePortableCaseModule.createCase.caseDirExists=Case folder {0} already exists",
|
||||
"CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case",
|
||||
"# {0} - folder",
|
||||
"CreatePortableCaseModule.createCase.errorCreatingFolder=Error creating folder {0}",
|
||||
})
|
||||
private void createCase(File outputDir, ReportProgressPanel progressPanel) {
|
||||
|
||||
// Create the case folder
|
||||
String caseName = currentCase.getDisplayName() + " (Portable)";
|
||||
caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
|
||||
|
||||
if (caseFolder.exists()) {
|
||||
handleError("Case folder " + caseFolder.toString() + " already exists",
|
||||
Bundle.CreatePortableCaseModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the case
|
||||
try {
|
||||
skCase = currentCase.createPortableCase(caseName, caseFolder);
|
||||
} catch (TskCoreException ex) {
|
||||
handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
|
||||
Bundle.CreatePortableCaseModule_createCase_errorCreatingCase(), ex, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the base folder for the copied files
|
||||
copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
|
||||
if (! copiedFilesFolder.mkdir()) {
|
||||
handleError("Error creating folder " + copiedFilesFolder.toString(),
|
||||
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create subfolders for the copied files
|
||||
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
|
||||
File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
|
||||
if (! subFolder.mkdir()) {
|
||||
handleError("Error creating folder " + subFolder.toString(),
|
||||
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
|
||||
if (! unknownTypeFolder.mkdir()) {
|
||||
handleError("Error creating folder " + unknownTypeFolder.toString(),
|
||||
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all files with a given tag to the portable case.
|
||||
*
|
||||
* @param oldTagName
|
||||
* @param progressPanel
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - File name",
|
||||
"CreatePortableCaseModule.addFilesToPortableCase.copyingFile=Copying file {0}",
|
||||
})
|
||||
private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
|
||||
|
||||
// Get all the tags in the current case
|
||||
List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
|
||||
|
||||
// Copy the files into the portable case and tag
|
||||
for (ContentTag tag : tags) {
|
||||
|
||||
// Check for cancellation
|
||||
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
|
||||
return;
|
||||
}
|
||||
|
||||
Content content = tag.getContent();
|
||||
if (content instanceof AbstractFile) {
|
||||
AbstractFile file = (AbstractFile) content;
|
||||
String filePath = file.getParentPath() + file.getName();
|
||||
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_addFilesToPortableCase_copyingFile(filePath));
|
||||
|
||||
long newFileId;
|
||||
CaseDbTransaction trans = skCase.beginTransaction();
|
||||
try {
|
||||
newFileId = copyContent(file, trans);
|
||||
trans.commit();
|
||||
} catch (TskCoreException ex) {
|
||||
trans.rollback();
|
||||
throw(ex);
|
||||
}
|
||||
|
||||
// Tag the file
|
||||
if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
|
||||
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName());
|
||||
}
|
||||
skCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object ID for the given content object in the portable case.
|
||||
*
|
||||
* @param content The content object to copy into the portable case
|
||||
* @param trans The current transaction
|
||||
*
|
||||
* @return the new object ID for this content
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private long copyContent(Content content, CaseDbTransaction trans) throws TskCoreException {
|
||||
|
||||
// Check if we've already copied this content
|
||||
if (oldIdToNewContent.containsKey(content.getId())) {
|
||||
return oldIdToNewContent.get(content.getId()).getId();
|
||||
}
|
||||
|
||||
// Otherwise:
|
||||
// - Make parent of this object (if applicable)
|
||||
// - Copy this content
|
||||
long parentId = 0;
|
||||
if (content.getParent() != null) {
|
||||
parentId = copyContent(content.getParent(), trans);
|
||||
}
|
||||
|
||||
Content newContent;
|
||||
if (content instanceof Image) {
|
||||
Image image = (Image)content;
|
||||
newContent = skCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
|
||||
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
|
||||
} else if (content instanceof VolumeSystem) {
|
||||
VolumeSystem vs = (VolumeSystem)content;
|
||||
newContent = skCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
|
||||
} else if (content instanceof Volume) {
|
||||
Volume vs = (Volume)content;
|
||||
newContent = skCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
|
||||
vs.getDescription(), vs.getFlags(), trans);
|
||||
} else if (content instanceof FileSystem) {
|
||||
FileSystem fs = (FileSystem)content;
|
||||
newContent = skCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
|
||||
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
|
||||
fs.getName(), trans);
|
||||
} else if (content instanceof AbstractFile) {
|
||||
AbstractFile abstractFile = (AbstractFile)content;
|
||||
|
||||
if (abstractFile instanceof LocalFilesDataSource) {
|
||||
LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
|
||||
newContent = skCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
|
||||
} else {
|
||||
if (abstractFile.isDir()) {
|
||||
newContent = skCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
|
||||
} else {
|
||||
try {
|
||||
// Copy the file
|
||||
String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
|
||||
String exportSubFolder = getExportSubfolder(abstractFile);
|
||||
File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
|
||||
File localFile = new File(exportFolder, fileName);
|
||||
ContentUtils.writeToFile(abstractFile, localFile);
|
||||
|
||||
// Get the new parent object in the portable case database
|
||||
Content oldParent = abstractFile.getParent();
|
||||
if (! oldIdToNewContent.containsKey(oldParent.getId())) {
|
||||
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created");
|
||||
}
|
||||
Content newParent = oldIdToNewContent.get(oldParent.getId());
|
||||
|
||||
// Construct the relative path to the copied file
|
||||
String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
|
||||
|
||||
newContent = skCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
|
||||
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
|
||||
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
|
||||
true, TskData.EncodingType.NONE,
|
||||
newParent, trans);
|
||||
} catch (IOException ex) {
|
||||
throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
|
||||
+ abstractFile.getId(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
|
||||
}
|
||||
|
||||
// Save the new object
|
||||
oldIdToNewContent.put(content.getId(), newContent);
|
||||
newIdToContent.put(newContent.getId(), newContent);
|
||||
return oldIdToNewContent.get(content.getId()).getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the subfolder name for this file based on MIME type
|
||||
*
|
||||
* @param abstractFile the file
|
||||
*
|
||||
* @return the name of the appropriate subfolder for this file type
|
||||
*/
|
||||
private String getExportSubfolder(AbstractFile abstractFile) {
|
||||
if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
|
||||
return UNKNOWN_FILE_TYPE_FOLDER;
|
||||
}
|
||||
|
||||
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
|
||||
if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
|
||||
return cat.getDisplayName();
|
||||
}
|
||||
}
|
||||
return UNKNOWN_FILE_TYPE_FOLDER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out the maps and other fields and close the database connections.
|
||||
*/
|
||||
private void cleanup() {
|
||||
oldIdToNewContent.clear();
|
||||
newIdToContent.clear();
|
||||
oldTagNameToNewTagName.clear();
|
||||
currentCase = null;
|
||||
if (skCase != null) {
|
||||
// We want to close the database connections here but it is currently not possible. JIRA-4736
|
||||
skCase = null;
|
||||
}
|
||||
caseFolder = null;
|
||||
copiedFilesFolder = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JPanel getConfigurationPanel() {
|
||||
configPanel = new CreatePortableCasePanel();
|
||||
return configPanel;
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?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">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="deselectAllButton" linkSize="1" max="32767" attributes="0"/>
|
||||
<Component id="selectAllButton" linkSize="1" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="outputFolderTextField" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chooseOutputFolderButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<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 max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" 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="selectAllButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="deselectAllButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane1" pref="132" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="outputFolderTextField" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="chooseOutputFolderButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="selectAllButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.selectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[99, 23]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[99, 23]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[99, 23]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectAllButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="deselectAllButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.deselectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deselectAllButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
|
||||
<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.JList" name="tagNamesListBox">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JTextField" name="outputFolderTextField">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.outputFolderTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="chooseOutputFolderButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.chooseOutputFolderButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="chooseOutputFolderButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.jLabel2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -0,0 +1,329 @@
|
||||
/*
|
||||
* 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.report;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.ListModel;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"CreatePortableCasePanel.error.errorTitle=Error getting tag names for case",
|
||||
"CreatePortableCasePanel.error.noOpenCase=There is no case open",
|
||||
"CreatePortableCasePanel.error.errorLoadingTags=Error loading tags",
|
||||
})
|
||||
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
|
||||
class CreatePortableCasePanel extends javax.swing.JPanel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private List<TagName> tagNames;
|
||||
private final Map<String, Boolean> tagNameSelections = new LinkedHashMap<>();
|
||||
private final TagNamesListModel tagsNamesListModel = new TagNamesListModel();
|
||||
private final TagsNamesListCellRenderer tagsNamesRenderer = new TagsNamesListCellRenderer();
|
||||
|
||||
/**
|
||||
* Creates new form CreatePortableCasePanel
|
||||
*/
|
||||
public CreatePortableCasePanel() {
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
populateTagNameComponents();
|
||||
|
||||
try {
|
||||
outputFolderTextField.setText(Case.getCurrentCaseThrows().getReportDirectory());
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex);
|
||||
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_noOpenCase(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateTagNameComponents() {
|
||||
// Get the tag names in use for the current case.
|
||||
try {
|
||||
tagNames = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse();
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex);
|
||||
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_errorLoadingTags(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex);
|
||||
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_noOpenCase(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
// Mark the tag names as unselected. Note that tagNameSelections is a
|
||||
// LinkedHashMap so that order is preserved and the tagNames and tagNameSelections
|
||||
// containers are "parallel" containers.
|
||||
for (TagName tagName : tagNames) {
|
||||
tagNameSelections.put(tagName.getDisplayName(), Boolean.FALSE);
|
||||
}
|
||||
|
||||
// Set up the tag names JList component to be a collection of check boxes
|
||||
// for selecting tag names. The mouse click listener updates tagNameSelections
|
||||
// to reflect user choices.
|
||||
tagNamesListBox.setModel(tagsNamesListModel);
|
||||
tagNamesListBox.setCellRenderer(tagsNamesRenderer);
|
||||
tagNamesListBox.setVisibleRowCount(-1);
|
||||
tagNamesListBox.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt) {
|
||||
JList<?> list = (JList) evt.getSource();
|
||||
int index = list.locationToIndex(evt.getPoint());
|
||||
if (index > -1) {
|
||||
String value = tagsNamesListModel.getElementAt(index);
|
||||
tagNameSelections.put(value, !tagNameSelections.get(value));
|
||||
list.repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subset of the tag names in use selected by the user.
|
||||
*
|
||||
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
|
||||
*/
|
||||
List<TagName> getSelectedTagNames() {
|
||||
List<TagName> selectedTagNames = new ArrayList<>();
|
||||
for (TagName tagName : tagNames) {
|
||||
if (tagNameSelections.get(tagName.getDisplayName())) {
|
||||
selectedTagNames.add(tagName);
|
||||
}
|
||||
}
|
||||
return selectedTagNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the output folder
|
||||
*
|
||||
* @return The output folder for the portable case
|
||||
*/
|
||||
String getOutputFolder() {
|
||||
return outputFolderTextField.getText();
|
||||
}
|
||||
|
||||
// This class is a list model for the tag names JList component.
|
||||
private class TagNamesListModel implements ListModel<String> {
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return tagNames.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementAt(int index) {
|
||||
return tagNames.get(index).getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListDataListener(ListDataListener l) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListDataListener(ListDataListener l) {
|
||||
}
|
||||
}
|
||||
|
||||
// This class renders the items in the tag names JList component as JCheckbox components.
|
||||
private class TagsNamesListCellRenderer extends JCheckBox implements ListCellRenderer<String> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends String> list, String value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
if (value != null) {
|
||||
setEnabled(list.isEnabled());
|
||||
setSelected(tagNameSelections.get(value));
|
||||
setFont(list.getFont());
|
||||
setBackground(list.getBackground());
|
||||
setForeground(list.getForeground());
|
||||
setText(value);
|
||||
return this;
|
||||
}
|
||||
return new JLabel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
selectAllButton = new javax.swing.JButton();
|
||||
deselectAllButton = new javax.swing.JButton();
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
tagNamesListBox = new javax.swing.JList<>();
|
||||
outputFolderTextField = new javax.swing.JTextField();
|
||||
chooseOutputFolderButton = new javax.swing.JButton();
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
jLabel2 = new javax.swing.JLabel();
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(selectAllButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.selectAllButton.text")); // NOI18N
|
||||
selectAllButton.setMaximumSize(new java.awt.Dimension(99, 23));
|
||||
selectAllButton.setMinimumSize(new java.awt.Dimension(99, 23));
|
||||
selectAllButton.setPreferredSize(new java.awt.Dimension(99, 23));
|
||||
selectAllButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
selectAllButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.deselectAllButton.text")); // NOI18N
|
||||
deselectAllButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
deselectAllButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jScrollPane1.setViewportView(tagNamesListBox);
|
||||
|
||||
outputFolderTextField.setText(org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.outputFolderTextField.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(chooseOutputFolderButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.chooseOutputFolderButton.text")); // NOI18N
|
||||
chooseOutputFolderButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
chooseOutputFolderButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.jLabel1.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.jLabel2.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel1)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel2)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addComponent(jScrollPane1))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(deselectAllButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(selectAllButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(outputFolderTextField)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chooseOutputFolderButton)))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deselectAllButton, selectAllButton});
|
||||
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jLabel1)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(selectAllButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(deselectAllButton))
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 132, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel2)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(outputFolderTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(chooseOutputFolderButton))
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed
|
||||
for (TagName tagName : tagNames) {
|
||||
tagNameSelections.put(tagName.getDisplayName(), Boolean.TRUE);
|
||||
}
|
||||
tagNamesListBox.repaint();
|
||||
}//GEN-LAST:event_selectAllButtonActionPerformed
|
||||
|
||||
private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed
|
||||
for (TagName tagName : tagNames) {
|
||||
tagNameSelections.put(tagName.getDisplayName(), Boolean.FALSE);
|
||||
}
|
||||
tagNamesListBox.repaint();
|
||||
}//GEN-LAST:event_deselectAllButtonActionPerformed
|
||||
|
||||
private void chooseOutputFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseOutputFolderButtonActionPerformed
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
|
||||
File currentSelection = new File(outputFolderTextField.getText());
|
||||
if (currentSelection.exists()) {
|
||||
fileChooser.setCurrentDirectory(currentSelection);
|
||||
}
|
||||
|
||||
int result = fileChooser.showOpenDialog(this);
|
||||
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
String outputDir = fileChooser.getSelectedFile().getAbsolutePath();
|
||||
outputFolderTextField.setText(outputDir);
|
||||
}
|
||||
}//GEN-LAST:event_chooseOutputFolderButtonActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton chooseOutputFolderButton;
|
||||
private javax.swing.JButton deselectAllButton;
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JLabel jLabel2;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JTextField outputFolderTextField;
|
||||
private javax.swing.JButton selectAllButton;
|
||||
private javax.swing.JList<String> tagNamesListBox;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
@ -63,7 +63,7 @@ public class ImageGalleryDatabaseUpdateService implements AutopsyService {
|
||||
return;
|
||||
}
|
||||
ProgressIndicator progress = context.getProgressIndicator();
|
||||
progress.start(Bundle.ImageGalleryDatabaseUpdateService_openCaseResources_progressMessage_start());
|
||||
progress.progress(Bundle.ImageGalleryDatabaseUpdateService_openCaseResources_progressMessage_start());
|
||||
try {
|
||||
ImageGalleryModule.createController(context.getCase());
|
||||
} catch (TskCoreException ex) {
|
||||
|
@ -1577,11 +1577,20 @@ public final class DrawableDB {
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the build status for the given data source.
|
||||
* Will return UNKNOWN if the data source is not yet in the database.
|
||||
*
|
||||
* @param dataSourceId
|
||||
*
|
||||
* @return The status of the data source or UKNOWN if it is not found.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException {
|
||||
Map<Long, DrawableDbBuildStatusEnum> statusMap = getDataSourceDbBuildStatus();
|
||||
if (statusMap.containsKey(dataSourceId) == false) {
|
||||
throw new TskCoreException("Data Source ID not found: " + dataSourceId);
|
||||
return DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
}
|
||||
return statusMap.get(dataSourceId);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<hr/>
|
||||
<p><i>Copyright © 2012-2018 Basis Technology. Generated on $date<br/>
|
||||
<p><i>Copyright © 2012-2019 Basis Technology. Generated on $date<br/>
|
||||
This work is licensed under a
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/">Creative Commons Attribution-Share Alike 3.0 United States License</a>.
|
||||
</i></p>
|
||||
|
@ -65,6 +65,7 @@ The following topics are available here:
|
||||
- \subpage live_triage_page
|
||||
- \subpage advanced_page
|
||||
- \subpage experimental_page
|
||||
- \subpage translations_page
|
||||
|
||||
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.
|
||||
|
||||
|
75
docs/doxygen-user/translations.dox
Normal file
75
docs/doxygen-user/translations.dox
Normal file
@ -0,0 +1,75 @@
|
||||
/*! \page translations_page Translating This Document
|
||||
|
||||
The Autopsy user base is global. You can help out by translating the UI and this documentation.
|
||||
|
||||
\section translations_doc Translating Documentation
|
||||
|
||||
This section outlines how to translate this user documentation. To translate, you will need:
|
||||
- A git account
|
||||
- Basic familiarity with git
|
||||
- Text editor
|
||||
|
||||
The Autopsy documentation is created by [Doxygen](http://www.doxygen.nl/) from ".dox" text files in the [docs/doxygen-user](https://github.com/sleuthkit/autopsy/tree/develop/docs/doxygen-user) folder in the Github repository.
|
||||
|
||||
The first step is to fork the [Autopsy Repository](https://github.com/sleuthkit/autopsy) into your Git account and make a clone of it into your environment so that you can make edits to the files.
|
||||
|
||||
As you are editing, you can review your documentation by installing Doxygen and running 'doxygen' from within the translations folder. It will save the HTML to the 'user-docs' folder.
|
||||
|
||||
\subsection translations_doc_start Translating To a New Language
|
||||
|
||||
If there is not already documentation in a language, then you need to make a copy of the entire English 'doxygen-user' folder and name it 'doxygen-user_AB' where AB is replaced by the 2 character [country code] (http://www.lingoes.net/en/translator/langcode.htm). For example, 'doxygen-user_fr' for French and 'doxygen-user_ja' for Japanese.
|
||||
|
||||
Edit the Doxyfile to update the OUTPUT_LANGUAGE field. For English it has:
|
||||
|
||||
\code
|
||||
OUTPUT_LANGUAGE = English
|
||||
\endcode
|
||||
|
||||
Now, simply start translating the English documents.
|
||||
|
||||
\subsection translations_doc_update Updating The Documentation
|
||||
|
||||
When new releases are made and the English documentation is updated, the other languages should be updated as well. To determine what has changed:
|
||||
- First, determine when the last time the documentation was changed. From a command line, you can change into the translated documentation folder and type:
|
||||
|
||||
\code
|
||||
$ cd docs/doxygen-user_fr
|
||||
$ git log -n 1 .
|
||||
commit 94e4b1042af47908dd4a0b2959b3f6c3d4af1111
|
||||
Author: John Doe <jdoe@sleuthkit.org>
|
||||
Date: Tue Jan 1 22:56:09 2019 -0500
|
||||
|
||||
update to quick start
|
||||
\endcode
|
||||
|
||||
This shows you that commit 94e4b1042af47908dd4a0b2959b3f6c3d4af1111 was the last translation update to occur for the French version.
|
||||
|
||||
- Next, determine what changed in the English version since then:
|
||||
|
||||
\code
|
||||
$ git diff 94e4b1042af47908dd4a0b2959b3f6c3d4af1111 ../doxygen-user
|
||||
diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox
|
||||
index 83d3407e8..e8cd01c1b 100644
|
||||
--- a/docs/doxygen-user/central_repo.dox
|
||||
+++ b/docs/doxygen-user/central_repo.dox
|
||||
@@ -79,6 +79,16 @@ Descriptions of the property types:
|
||||
- Phone numbers are currently only extracted from call logs, contact lists and message, which come from the Android Analyzer module.
|
||||
- <b>USB Devices</b>
|
||||
- USB device properties come from the registry parsing in the Recent Activity Module.
|
||||
+- <b>Wireless Networks</b>
|
||||
+ - Wireless networks are correlated on SSIDs, and come from the registry par
|
||||
\endcode
|
||||
|
||||
- Update the translated documentation accordingly based on what changed in the English version.
|
||||
|
||||
- If you do not get to complete all of the changes, you should create a TODO.txt file that lists what was not updated so that other people know that not everything was updated.
|
||||
|
||||
\subsection translations_doc_commit Committing the Documentation
|
||||
|
||||
You should submit a Github Pull Request when:
|
||||
- You complete a language.
|
||||
- You don't have time to do more work, but want to submit what you did.
|
||||
|
||||
To get the code committed, send a [pull request](https://help.github.com/articles/about-pull-requests/) to the main Autopsy repository.
|
||||
|
||||
*/
|
@ -39,17 +39,26 @@ def gitSleuthkitCheckout(branch, branchOwner):
|
||||
# passed is a global variable that gets set to non-zero integer
|
||||
# When an error occurs
|
||||
global passed
|
||||
if (branchOwner==ORIGIN_OWNER):
|
||||
cmd = ['git','checkout', branch]
|
||||
else:
|
||||
if branch in getSleuthkitBranchList(branchOwner):
|
||||
#add the remotes
|
||||
#if the branch owner was origin substitute in the name of that owner
|
||||
if (branchOwner==ORIGIN_OWNER):
|
||||
gitHubUser="sleuthkit"
|
||||
else:
|
||||
gitHubUser=branchOwner
|
||||
checkout=['git','checkout','-b',branchOwner+'-'+branch]
|
||||
print("Command run:" + " ".join(checkout))
|
||||
passed = subprocess.call(checkout, stdout=sys.stdout,cwd=TSK_HOME)
|
||||
cmd = ['git','pull', "/".join(["https://github.com", branchOwner, "sleuthkit.git"]), branch]
|
||||
cmd = ['git','pull', "/".join(["https://github.com", gitHubUser, "sleuthkit.git"]), branch]
|
||||
if passed != 0: #0 would be success
|
||||
#unable to create new branch return instead of pulling
|
||||
return
|
||||
print("Command run:" + " ".join(cmd))
|
||||
passed = subprocess.call(cmd,stdout=sys.stdout,cwd=TSK_HOME)
|
||||
if (passed == 0):
|
||||
sys.exit() #exit if successful
|
||||
else:
|
||||
print("Branch: " + branch + " does not exist for owner: " + branchOwner)
|
||||
|
||||
def parseXML(xmlFile):
|
||||
'''
|
||||
@ -70,39 +79,38 @@ def main():
|
||||
if not TSK_HOME:
|
||||
sys.exit(1)
|
||||
print('Please set TSK_HOME env variable')
|
||||
|
||||
# Get the Autopsy branch being used. Travis and Appveyor
|
||||
# will tell us where a pull request is directed
|
||||
TRAVIS=os.getenv("TRAVIS",False)
|
||||
APPVEYOR=os.getenv("APPVEYOR",False)
|
||||
if TRAVIS == "true":
|
||||
CURRENT_BRANCH=os.getenv("TRAVIS_PULL_REQUEST_BRANCH",False)
|
||||
BRANCH_OWNER=os.getenv("TRAVIS_PULL_REQUEST_SLUG", False).split('/')[0]
|
||||
CURRENT_BRANCH=os.getenv("TRAVIS_PULL_REQUEST_BRANCH","") #make default empty string which is same vaule used when not a PR
|
||||
if (CURRENT_BRANCH != ""): #if it is a PR
|
||||
BRANCH_OWNER=os.getenv("TRAVIS_PULL_REQUEST_SLUG", ORIGIN_OWNER+"/"+CURRENT_BRANCH).split('/')[0] #default owner is ORIGIN_OWNER
|
||||
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
|
||||
TARGET_BRANCH=os.getenv("TRAVIS_BRANCH",DEVELOP_BRANCH)
|
||||
elif APPVEYOR:
|
||||
CURRENT_BRANCH=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH",False)
|
||||
BRANCH_OWNER=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME", False).split('/')[0]
|
||||
CURRENT_BRANCH=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH","") #make default same as value used by travis for readability of code
|
||||
if (CURRENT_BRANCH != ""): #if it is a PR
|
||||
BRANCH_OWNER=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME", ORIGIN_OWNER+"/"+CURRENT_BRANCH).split('/')[0] #default owner is ORIGIN_OWNER
|
||||
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
|
||||
TARGET_BRANCH=os.getenv("APPVEYOR_REPO_BRANCH",DEVELOP_BRANCH)
|
||||
else:
|
||||
cmd=['git','rev-parse','--abbrev-ref','HEAD']
|
||||
output = subprocess.check_output(cmd)
|
||||
CURRENT_BRANCH=output.strip()
|
||||
BRANCH_OWNER=ORIGIN_OWNER
|
||||
TARGET_BRANCH=output.strip()
|
||||
# If we are in an Autopsy release branch, then use the
|
||||
# info in TSKVersion.xml to find the corresponding TSK
|
||||
# release branch. For other branches, we don't always
|
||||
# trust that TSKVersion has been updated.
|
||||
if CURRENT_BRANCH.startswith('release'):
|
||||
if TARGET_BRANCH.startswith('release'):
|
||||
version = parseXML('TSKVersion.xml')
|
||||
RELEASE_BRANCH = "release-"+version
|
||||
gitSleuthkitCheckout(RELEASE_BRANCH, BRANCH_OWNER)
|
||||
#If it failed try the origin release branch
|
||||
if passed != 0:
|
||||
#Check if the same user has a release branch which corresponds to this release branch
|
||||
gitSleuthkitCheckout(RELEASE_BRANCH, ORIGIN_OWNER)
|
||||
# Check if the same branch exists in TSK (develop->develop, custom1->custom1, etc.)
|
||||
else:
|
||||
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
|
||||
|
||||
gitSleuthkitCheckout(TARGET_BRANCH, ORIGIN_OWNER)
|
||||
# Otherwise, default to origin develop
|
||||
if passed != 0:
|
||||
gitSleuthkitCheckout(DEVELOP_BRANCH, ORIGIN_OWNER)
|
||||
|
||||
if passed != 0:
|
||||
|
Loading…
x
Reference in New Issue
Block a user