From ba9e8c2b8ac179951b648d6cbb4c9fb3fd76c03d Mon Sep 17 00:00:00 2001 From: Eammon Date: Wed, 16 Oct 2019 16:42:33 -0400 Subject: [PATCH 01/18] Workarounds to ensure that dialogs are not hidden on macOS. --- .../casemodule/CaseInformationPanel.java | 2 ++ .../autopsy/casemodule/CaseOpenAction.java | 17 +++++++++++++++-- .../autopsy/casemodule/CueBannerPanel.java | 2 ++ .../autopsy/casemodule/NewCaseWizardAction.java | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index a6494fe22b..76b56138d7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -162,6 +162,8 @@ class CaseInformationPanel extends javax.swing.JPanel { editCasePropertiesDialog.setResizable(true); editCasePropertiesDialog.pack(); editCasePropertiesDialog.setLocationRelativeTo(this); + // Workaround to ensure dialog is not hidden on macOS + editCasePropertiesDialog.setAlwaysOnTop(true); editCasePropertiesDialog.setVisible(true); editCasePropertiesDialog.toFront(); caseDetailsPanel.updateCaseInfo(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index cc07148ba0..b68d1a49f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -84,10 +84,16 @@ public final class CaseOpenAction extends CallableSystemAction implements Action fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileFilter(caseMetadataFileFilter); + if (null != ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE)) { fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); //NON-NLS } - + + /** + * If the open multi user case dialog is open make sure it's not set + * to always be on top as this hides the file chooser on macOS. + */ + OpenMultiUserCaseDialog.getInstance().setAlwaysOnTop(false); String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { @@ -95,7 +101,12 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * Pop up a file chooser to allow the user to select a case metadata * file (.aut file). */ - int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); + /** + * Passing the fileChooser as its own parent gets around an issue + * where the fileChooser was hidden behind the CueBannerPanel ("Welcome" dialog) + * on macOS. + */ + int retval = fileChooser.showOpenDialog(fileChooser); if (retval == JFileChooser.APPROVE_OPTION) { /* * Close the startup window, if it is open. @@ -159,6 +170,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action OpenMultiUserCaseDialog multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance(); multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + // Workaround to ensure that dialog is not hidden on macOS. + multiUserCaseWindow.setAlwaysOnTop(true); multiUserCaseWindow.setVisible(true); WindowManager.getDefault().getMainWindow().setCursor(null); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java index 3ddb97fcfd..37c6c7c8b8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java @@ -249,6 +249,8 @@ public class CueBannerPanel extends javax.swing.JPanel { private void openRecentCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openRecentCaseButtonActionPerformed recentCasesWindow.setLocationRelativeTo(this); OpenRecentCasePanel.getInstance(); //refreshes the recent cases table + // Workaround to ensure that dialog is not hidden on macOS. + recentCasesWindow.setAlwaysOnTop(true); recentCasesWindow.setVisible(true); }//GEN-LAST:event_openRecentCaseButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 50688b1ac1..c5e6ece78d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -71,6 +71,8 @@ final class NewCaseWizardAction extends CallableSystemAction { wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text")); Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); + // Workaround to ensure new case dialog is not hidden on macOS + dialog.setAlwaysOnTop(true); dialog.setVisible(true); dialog.toFront(); if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) { From f73eb23ad966d49d42e87f9030e369243f5ed1e8 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 16 Oct 2019 23:18:24 -0400 Subject: [PATCH 02/18] Update ThunderbirdMboxFileIngestModule.java Check threaded messageId if null and skip it if it is. Format code also. --- .../ThunderbirdMboxFileIngestModule.java | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 5c42269a8a..b27f045398 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -65,12 +65,13 @@ import org.sleuthkit.datamodel.TskException; * structure and metadata. */ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { + private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private FileManager fileManager; private IngestJobContext context; private Blackboard blackboard; - + private Case currentCase; /** @@ -80,7 +81,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override - @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) + @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; try { @@ -103,8 +104,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } //skip unalloc - if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) || - (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) + || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -115,7 +116,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { // check its signature boolean isMbox = false; boolean isEMLFile = false; - + try { byte[] t = new byte[64]; if (abstractFile.getSize() > 64) { @@ -132,7 +133,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isMbox) { return processMBox(abstractFile); } - + if (isEMLFile) { return processEMLFile(abstractFile); } @@ -140,7 +141,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (PstParser.isPstFile(abstractFile)) { return processPst(abstractFile); } - + if (VcardParser.isVcardFile(abstractFile)) { return processVcard(abstractFile); } @@ -160,7 +161,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -188,11 +189,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { PstParser parser = new PstParser(services); PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - switch( result) { + switch (result) { case OK: Iterator pstMsgIterator = parser.getEmailMessageIterator(); if (pstMsgIterator != null) { - processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile); + processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); } else { // sometimes parser returns ParseResult=OK but there are no messages postErrorMessage( @@ -273,7 +274,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -298,16 +299,16 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); + MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()); List emails = new ArrayList<>(); - if(emailIterator != null) { - while(emailIterator.hasNext()) { + if (emailIterator != null) { + while (emailIterator.hasNext()) { EmailMessage emailMessage = emailIterator.next(); - if(emailMessage != null) { + if (emailMessage != null) { emails.add(emailMessage); } } - + String errors = emailIterator.getErrors(); if (!errors.isEmpty()) { postErrorMessage( @@ -315,7 +316,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { abstractFile.getName()), errors); } } - processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); + processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile); if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS @@ -323,7 +324,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - + /** * Parse and extract data from a vCard file. * @@ -347,8 +348,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } return ProcessResult.OK; } - - private ProcessResult processEMLFile(AbstractFile abstractFile) { + + private ProcessResult processEMLFile(AbstractFile abstractFile) { try { EmailMessage message = EMLParser.parse(abstractFile); @@ -400,7 +401,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a module output folder. - * + * * @throws NoCurrentCaseException if there is no open case. * * @return the module output folder @@ -435,38 +436,40 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param abstractFile */ private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { - + // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. - try{ + try { EmailMessageThreader.threadMessages(partialEmailsForThreading); - } catch(Exception ex) { + } catch (Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - + List derivedFiles = new ArrayList<>(); int msgCnt = 0; - while(fullMessageIterator.hasNext()) { + while (fullMessageIterator.hasNext()) { EmailMessage current = fullMessageIterator.next(); - - if(current == null) { + + if (current == null) { continue; } - if(partialEmailsForThreading.size() > msgCnt) { + if (partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - - if(threaded.getMessageID().equals(current.getMessageID()) && - threaded.getSubject().equals(current.getSubject())) { - current.setMessageThreadID(threaded.getMessageThreadID()); + + if (threaded.getMessageID() != null) { + if (threaded.getMessageID().equals(current.getMessageID()) + && threaded.getSubject().equals(current.getSubject())) { + current.setMessageThreadID(threaded.getMessageThreadID()); + } } } - + BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); - - if ((msgArtifact != null) && (current.hasAttachment())) { - derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); + + if ((msgArtifact != null) && (current.hasAttachment())) { + derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact)); } } @@ -477,6 +480,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } context.addFilesToJob(derivedFiles); } + /** * Add the given attachments as derived files and reschedule them for * ingest. @@ -517,29 +521,30 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } /** - * Finds and returns a set of unique email addresses found in the input string + * Finds and returns a set of unique email addresses found in the input + * string * * @param input - input string, like the To/CC line from an email header - * + * * @return Set: set of email addresses found in the input string */ private Set findEmailAddresess(String input) { Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", - Pattern.CASE_INSENSITIVE); + Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); Set emailAddresses = new HashSet<>(); while (m.find()) { - emailAddresses.add( m.group()); + emailAddresses.add(m.group()); } return emailAddresses; } - + /** * Add a blackboard artifact for the given e-mail message. * * @param email The e-mail message. * @param abstractFile The associated file. - * + * * @return The generated e-mail message artifact. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) @@ -563,73 +568,69 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List senderAddressList = new ArrayList<>(); String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - + AccountFileInstance senderAccountInstance = null; if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { senderAccountInstance = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS } - catch(TskCoreException ex) { - logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS - } + } else { + logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS } - else { - logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS - } - + List recipientAddresses = new ArrayList<>(); recipientAddresses.addAll(findEmailAddresess(to)); recipientAddresses.addAll(findEmailAddresess(cc)); recipientAddresses.addAll(findEmailAddresess(bcc)); - + List recipientAccountInstances = new ArrayList<>(); recipientAddresses.forEach((addr) -> { try { - AccountFileInstance recipientAccountInstance = - currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, - EmailParserModuleFactory.getModuleName(), abstractFile); + AccountFileInstance recipientAccountInstance + = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + EmailParserModuleFactory.getModuleName(), abstractFile); recipientAccountInstances.add(recipientAccountInstance); - } - catch(TskCoreException ex) { + } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS } }); - + addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - + addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - + addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); - - addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + + addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); - - addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), + + addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), ATTRIBUTE_TYPE.TSK_PATH, bbattributes); - + addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); - - + try { - + bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); // Add account relationships - currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); - + currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL); + try { // index the artifact for keyword search - blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); @@ -640,11 +641,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return bbart; } - + /** * Add an attribute of a specified type to a supplied Collection. - * - * @param stringVal The attribute value. + * + * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. */ @@ -656,7 +657,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -666,10 +667,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); } } - + /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param longVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -679,10 +680,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); } } - + /** * Post an error message for the user. - * + * * @param subj The error subject. * @param details The error details. */ @@ -693,7 +694,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get the IngestServices object. - * + * * @return The IngestServices object. */ IngestServices getServices() { From 9f3e258837e9826fb8e14cea95049dd93d01c9e7 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 17 Oct 2019 02:13:46 -0400 Subject: [PATCH 03/18] Update ThunderbirdMboxFileIngestModule.java Make codacy happy. --- .../thunderbirdparser/ThunderbirdMboxFileIngestModule.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index b27f045398..91bf391e63 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -458,12 +458,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - if (threaded.getMessageID() != null) { - if (threaded.getMessageID().equals(current.getMessageID()) - && threaded.getSubject().equals(current.getSubject())) { + if ((threaded.getMessageID() != null) && + (threaded.getMessageID().equals(current.getMessageID()) + && threaded.getSubject().equals(current.getSubject()))) { current.setMessageThreadID(threaded.getMessageThreadID()); } - } } BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); From d39dc4232920963ecb8dc4ff5199eed964b8cd45 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 21 Oct 2019 17:19:26 -0400 Subject: [PATCH 04/18] Created a new View for the JSlider. Made the slider thumb easier to drag. Added track highlighting. Adding thread safety to the progress slider. --- .../contentviewers/MediaPlayerPanel.form | 10 +- .../contentviewers/MediaPlayerPanel.java | 249 ++++++++++++++++-- 2 files changed, 240 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 605ed93697..d8433a907b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -16,8 +16,8 @@ - + @@ -106,6 +106,9 @@ + + + @@ -172,7 +175,7 @@ - + @@ -192,6 +195,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index dec8bf55b4..be600a0ea6 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -19,8 +19,16 @@ package org.sleuthkit.autopsy.contentviewers; import com.google.common.io.Files; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -30,6 +38,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javax.swing.BoxLayout; @@ -52,13 +61,17 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; +import javax.swing.JComponent; +import javax.swing.JSlider; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicSliderUI; import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; import org.freedesktop.gstreamer.event.SeekType; +import org.openide.util.Exceptions; /** * This is a video player that is part of the Media View layered pane. It uses @@ -188,6 +201,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; + + //Serialize setting the value of the Video progress slider. + //The slider is a shared resource between the VideoPanelUpdater + //and the TrackListener of the JSliderUI. + private final Semaphore sliderLock; /** * Creates new form MediaViewVideoPanel @@ -195,6 +213,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError { initComponents(); customizeComponents(); + sliderLock = new Semaphore(1); } private void customizeComponents() { @@ -531,26 +550,220 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { - if (!progressSlider.getValueIsAdjusting()) { - long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - /** - * Duration may not be known until there is video data in the - * pipeline. We start this updater when data-flow has just been - * initiated so buffering may still be in progress. - */ - if (duration >= 0 && position >= 0) { - double relativePosition = (double) position / duration; - progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); - } + try { + if (!progressSlider.getValueIsAdjusting()) { + sliderLock.acquire(); + long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + /** + * Duration may not be known until there is video data in the + * pipeline. We start this updater when data-flow has just been + * initiated so buffering may still be in progress. + */ + if (duration >= 0 && position >= 0) { + double relativePosition = (double) position / duration; + progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); + } - SwingUtilities.invokeLater(() -> { - updateTimeLabel(position, duration); - }); + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); + sliderLock.release(); + } + } catch (InterruptedException ex) { + } } } + /** + * Represents the default configuration for the circular JSliderUI. + */ + private class CircularJSliderConfiguration { + + //Thumb configurations + private final Color thumbColor; + private final Dimension thumbDimension; + + //Track configurations + //Progress bar can be bisected into a seen group + //and an unseen group. + private final Color unseen; + private final Color seen; + + /** + * Default configuration + * + * JSlider is light blue RGB(0,130,255). Seen track is light blue + * RGB(0,130,255). Unseen track is light grey RGB(192, 192, 192). + * + * @param thumbDimension Size of the oval thumb. + */ + public CircularJSliderConfiguration(Dimension thumbDimension) { + Color lightBlue = new Color(0, 130, 255); + + seen = lightBlue; + unseen = Color.LIGHT_GRAY; + + thumbColor = lightBlue; + + this.thumbDimension = new Dimension(thumbDimension); + } + + public Color getThumbColor() { + return thumbColor; + } + + public Color getUnseenTrackColor() { + return unseen; + } + + public Color getSeenTrackColor() { + return seen; + } + + public Dimension getThumbDimension() { + return new Dimension(thumbDimension); + } + } + + /** + * Custom view for the JSlider. + */ + private class CircularJSliderUI extends BasicSliderUI { + + private final CircularJSliderConfiguration config; + + /** + * Creates a custom view for the JSlider. This view draws a blue oval + * thumb at the given width and height. It also paints the track blue as + * the thumb progresses. + * + * @param b JSlider component + * @param width Width of the oval + * @param height Height of the oval. + */ + public CircularJSliderUI(JSlider b, CircularJSliderConfiguration config) { + super(b); + this.config = config; + } + + @Override + protected Dimension getThumbSize() { + return config.getThumbDimension(); + } + + /** + * Modifies the View to be an oval rather than the + * rectangle Controller. + */ + @Override + public void paintThumb(Graphics g) { + Rectangle thumb = this.thumbRect; + + Color original = g.getColor(); + + //Change the thumb view from the rectangle + //controller to an oval. + g.setColor(config.getThumbColor()); + Dimension thumbDimension = config.getThumbDimension(); + g.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); + + //Preserve the graphics original color + g.setColor(original); + } + + @Override + public void paintTrack(Graphics g) { + //This rectangle is the bounding box for the progress bar + //portion of the slider. The track is painted in the middle + //of this rectangle and the thumb laid overtop. + Rectangle track = this.trackRect; + + //Get the location of the thumb, this point splits the + //progress bar into 2 line segments, seen and unseen. + Rectangle thumb = this.thumbRect; + int thumbX = thumb.x; + int thumbY = thumb.y; + + Color original = g.getColor(); + + //Paint the seen side + g.setColor(config.getSeenTrackColor()); + g.drawLine(track.x, track.y + track.height / 2, + thumbX, thumbY + track.height / 2); + + //Paint the unseen side + g.setColor(config.getUnseenTrackColor()); + g.drawLine(thumbX, thumbY + track.height / 2, + track.x + track.width, track.y + track.height / 2); + + //Preserve the graphics color. + g.setColor(original); + } + + @Override + protected TrackListener createTrackListener(JSlider slider) { + return new CustomTrackListener(); + } + + @Override + protected void scrollDueToClickInTrack(int direction) { + try { + //Set the thumb position to the mouse press location, as opposed + //to the closest "block" which is the default behavior. + Point mousePosition = slider.getMousePosition(); + if (mousePosition == null) { + return; + } + int value = this.valueForXPosition(mousePosition.x); + + //Lock the slider down, which is a shared resource. + //The VideoPanelUpdater (dedicated thread) keeps the + //slider in sync with the video position, so without + //proper locking our change could be overwritten. + sliderLock.acquire(); + slider.setValueIsAdjusting(true); + slider.setValue(value); + slider.setValueIsAdjusting(false); + sliderLock.release(); + } catch (InterruptedException ex) { + } + } + + /** + * Applies anti-aliasing if available. + */ + @Override + public void update(Graphics g, JComponent c) { + if (g instanceof Graphics2D) { + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + super.update(g, c); + } + + /** + * This track listener will force the thumb to be snapped to the + * mouse location. This makes grabbing and dragging the JSlider much + * easier. Using the default track listener, the user would have to + * click exactly on the slider thumb to drag it. Now the thumb positions + * itself under the mouse so that it can always be dragged. + */ + private class CustomTrackListener extends CircularJSliderUI.TrackListener { + @Override + public void mousePressed(MouseEvent e) { + //Snap the thumb to position of the mouse + scrollDueToClickInTrack(0); + + //Handle the event as normal. + super.mousePressed(e); + } + } + } + /** * 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 @@ -592,6 +805,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setDoubleBuffered(true); progressSlider.setMinimumSize(new java.awt.Dimension(36, 21)); progressSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18)))); org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N @@ -645,7 +859,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.ipadx = 8; gridBagConstraints.ipady = 7; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0); + gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0); buttonPanel.add(VolumeIcon, gridBagConstraints); audioSlider.setMajorTickSpacing(10); @@ -655,6 +869,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie audioSlider.setValue(25); audioSlider.setMinimumSize(new java.awt.Dimension(200, 21)); audioSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15)))); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; gridBagConstraints.gridy = 0; @@ -739,8 +954,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) From 0573fc4a15292f80a081dced2cdc6cc1e4dd0003 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 21 Oct 2019 17:32:13 -0400 Subject: [PATCH 05/18] Updated javadoc and cleared out all 1 letter variable names --- .../contentviewers/MediaPlayerPanel.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index be600a0ea6..95634f0435 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -71,7 +71,6 @@ import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; import org.freedesktop.gstreamer.event.SeekType; -import org.openide.util.Exceptions; /** * This is a video player that is part of the Media View layered pane. It uses @@ -571,7 +570,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie sliderLock.release(); } } catch (InterruptedException ex) { - + //Video panel thread interrupted while waiting on lock. } } } @@ -640,11 +639,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * the thumb progresses. * * @param b JSlider component - * @param width Width of the oval - * @param height Height of the oval. + * @param config Configuration object. Contains info about thumb dimensions + * and colors. */ - public CircularJSliderUI(JSlider b, CircularJSliderConfiguration config) { - super(b); + public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) { + super(slider); this.config = config; } @@ -658,23 +657,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * rectangle Controller. */ @Override - public void paintThumb(Graphics g) { + public void paintThumb(Graphics graphic) { Rectangle thumb = this.thumbRect; - Color original = g.getColor(); + Color original = graphic.getColor(); //Change the thumb view from the rectangle //controller to an oval. - g.setColor(config.getThumbColor()); + graphic.setColor(config.getThumbColor()); Dimension thumbDimension = config.getThumbDimension(); - g.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); + graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); //Preserve the graphics original color - g.setColor(original); + graphic.setColor(original); } @Override - public void paintTrack(Graphics g) { + public void paintTrack(Graphics graphic) { //This rectangle is the bounding box for the progress bar //portion of the slider. The track is painted in the middle //of this rectangle and the thumb laid overtop. @@ -686,20 +685,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie int thumbX = thumb.x; int thumbY = thumb.y; - Color original = g.getColor(); + Color original = graphic.getColor(); //Paint the seen side - g.setColor(config.getSeenTrackColor()); - g.drawLine(track.x, track.y + track.height / 2, + graphic.setColor(config.getSeenTrackColor()); + graphic.drawLine(track.x, track.y + track.height / 2, thumbX, thumbY + track.height / 2); //Paint the unseen side - g.setColor(config.getUnseenTrackColor()); - g.drawLine(thumbX, thumbY + track.height / 2, + graphic.setColor(config.getUnseenTrackColor()); + graphic.drawLine(thumbX, thumbY + track.height / 2, track.x + track.width, track.y + track.height / 2); //Preserve the graphics color. - g.setColor(original); + graphic.setColor(original); } @Override @@ -728,6 +727,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie slider.setValueIsAdjusting(false); sliderLock.release(); } catch (InterruptedException ex) { + //Thread (EDT) interrupted while waiting on lock. } } @@ -735,14 +735,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * Applies anti-aliasing if available. */ @Override - public void update(Graphics g, JComponent c) { - if (g instanceof Graphics2D) { - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + public void update(Graphics graphic, JComponent component) { + if (graphic instanceof Graphics2D) { + Graphics2D graphic2 = (Graphics2D) graphic; + graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } - super.update(g, c); + super.update(graphic, component); } /** From 756c82256f904996e7021d98359efa1ebb9892c5 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 10:57:16 -0400 Subject: [PATCH 06/18] Paused the video when users click in the track. Unpause when mouse is released. This is the behavior of YouTube. It also keeps the video from ending during a drag. --- .../autopsy/contentviewers/MediaPlayerPanel.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 95634f0435..56feadbb82 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -757,10 +757,21 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public void mousePressed(MouseEvent e) { //Snap the thumb to position of the mouse scrollDueToClickInTrack(0); + + //Pause the video for convenience + gstPlayBin.pause(); //Handle the event as normal. super.mousePressed(e); - } + } + + @Override + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + + //Unpause once the mouse has been released. + gstPlayBin.play(); + } } } From f1b9d2ab85419a49379c572c564e05d07893086a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 11:08:10 -0400 Subject: [PATCH 07/18] Ran source formatting and disable track listening when the slider is not enabled. --- .../contentviewers/MediaPlayerPanel.java | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 56feadbb82..966d5829e6 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -200,7 +200,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; - + //Serialize setting the value of the Video progress slider. //The slider is a shared resource between the VideoPanelUpdater //and the TrackListener of the JSliderUI. @@ -555,9 +555,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); /** - * Duration may not be known until there is video data in the - * pipeline. We start this updater when data-flow has just been - * initiated so buffering may still be in progress. + * Duration may not be known until there is video data in + * the pipeline. We start this updater when data-flow has + * just been initiated so buffering may still be in + * progress. */ if (duration >= 0 && position >= 0) { double relativePosition = (double) position / duration; @@ -639,8 +640,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * the thumb progresses. * * @param b JSlider component - * @param config Configuration object. Contains info about thumb dimensions - * and colors. + * @param config Configuration object. Contains info about thumb + * dimensions and colors. */ public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) { super(slider); @@ -653,8 +654,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } /** - * Modifies the View to be an oval rather than the - * rectangle Controller. + * Modifies the View to be an oval rather than the rectangle Controller. */ @Override public void paintThumb(Graphics graphic) { @@ -700,7 +700,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Preserve the graphics color. graphic.setColor(original); } - + @Override protected TrackListener createTrackListener(JSlider slider) { return new CustomTrackListener(); @@ -716,7 +716,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie return; } int value = this.valueForXPosition(mousePosition.x); - + //Lock the slider down, which is a shared resource. //The VideoPanelUpdater (dedicated thread) keeps the //slider in sync with the video position, so without @@ -744,31 +744,39 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie super.update(graphic, component); } - + /** - * This track listener will force the thumb to be snapped to the - * mouse location. This makes grabbing and dragging the JSlider much - * easier. Using the default track listener, the user would have to - * click exactly on the slider thumb to drag it. Now the thumb positions + * This track listener will force the thumb to be snapped to the mouse + * location. This makes grabbing and dragging the JSlider much easier. + * Using the default track listener, the user would have to click + * exactly on the slider thumb to drag it. Now the thumb positions * itself under the mouse so that it can always be dragged. */ private class CustomTrackListener extends CircularJSliderUI.TrackListener { - @Override + + @Override public void mousePressed(MouseEvent e) { + if (!slider.isEnabled()) { + return; + } //Snap the thumb to position of the mouse scrollDueToClickInTrack(0); - + //Pause the video for convenience gstPlayBin.pause(); //Handle the event as normal. super.mousePressed(e); } - + @Override public void mouseReleased(MouseEvent e) { - super.mouseReleased(e); + if (!slider.isEnabled()) { + return; + } + super.mouseReleased(e); + //Unpause once the mouse has been released. gstPlayBin.play(); } From 30c18c15bfdb829a4c1807b37cd316afdde50029 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 13:05:47 -0400 Subject: [PATCH 08/18] Added fairness to semaphore and changed the acquire to not be interruptable. --- .../contentviewers/MediaPlayerPanel.java | 82 +++++++++---------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 966d5829e6..1f02708f64 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -66,6 +66,7 @@ import javax.swing.JSlider; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import javax.swing.plaf.basic.BasicSliderUI; +import javax.swing.plaf.basic.BasicSliderUI.TrackListener; import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; @@ -212,7 +213,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError { initComponents(); customizeComponents(); - sliderLock = new Semaphore(1); + //True for fairness. In other words, + //acquire() calls are processed in order of invocation. + sliderLock = new Semaphore(1, true); } private void customizeComponents() { @@ -549,29 +552,24 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { - try { - if (!progressSlider.getValueIsAdjusting()) { - sliderLock.acquire(); - long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - /** - * Duration may not be known until there is video data in - * the pipeline. We start this updater when data-flow has - * just been initiated so buffering may still be in - * progress. - */ - if (duration >= 0 && position >= 0) { - double relativePosition = (double) position / duration; - progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); - } - - SwingUtilities.invokeLater(() -> { - updateTimeLabel(position, duration); - }); - sliderLock.release(); + if (!progressSlider.getValueIsAdjusting()) { + sliderLock.acquireUninterruptibly(); + long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + /** + * Duration may not be known until there is video data in the + * pipeline. We start this updater when data-flow has just been + * initiated so buffering may still be in progress. + */ + if (duration >= 0 && position >= 0) { + double relativePosition = (double) position / duration; + progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } - } catch (InterruptedException ex) { - //Video panel thread interrupted while waiting on lock. + + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); + sliderLock.release(); } } } @@ -708,27 +706,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override protected void scrollDueToClickInTrack(int direction) { - try { - //Set the thumb position to the mouse press location, as opposed - //to the closest "block" which is the default behavior. - Point mousePosition = slider.getMousePosition(); - if (mousePosition == null) { - return; - } - int value = this.valueForXPosition(mousePosition.x); - - //Lock the slider down, which is a shared resource. - //The VideoPanelUpdater (dedicated thread) keeps the - //slider in sync with the video position, so without - //proper locking our change could be overwritten. - sliderLock.acquire(); - slider.setValueIsAdjusting(true); - slider.setValue(value); - slider.setValueIsAdjusting(false); - sliderLock.release(); - } catch (InterruptedException ex) { - //Thread (EDT) interrupted while waiting on lock. + //Set the thumb position to the mouse press location, as opposed + //to the closest "block" which is the default behavior. + Point mousePosition = slider.getMousePosition(); + if (mousePosition == null) { + return; } + int value = this.valueForXPosition(mousePosition.x); + + //Lock the slider down, which is a shared resource. + //The VideoPanelUpdater (dedicated thread) keeps the + //slider in sync with the video position, so without + //proper locking our change could be overwritten. + sliderLock.acquireUninterruptibly(); + slider.setValueIsAdjusting(true); + slider.setValue(value); + slider.setValueIsAdjusting(false); + sliderLock.release(); } /** @@ -774,7 +768,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (!slider.isEnabled()) { return; } - + super.mouseReleased(e); //Unpause once the mouse has been released. From 750c8b86fb9387ed7fa6e7f71787e567a5000501 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 14:20:00 -0400 Subject: [PATCH 09/18] Fix DataContentViewerUtility.java formatting --- .../DataContentViewerUtility.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index 24f54fa7ac..ea1e7b58a0 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. + * + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -23,32 +23,32 @@ import org.openide.nodes.Node; import org.sleuthkit.datamodel.BlackboardArtifact; /** - * Utility classes for content viewers. - * In theory, this would live in the contentviewer package, - * but the initial method was needed only be viewers in + * Utility classes for content viewers. In theory, this would live in the + * contentviewer package, but the initial method was needed only be viewers in * corecomponents and therefore can stay out of public API. */ public class DataContentViewerUtility { + /** - * Returns the first non-Blackboard Artifact from a Node. - * Needed for (at least) Hex and Strings that want to view - * all types of content (not just AbstractFile), but don't want - * to display an artifact unless that's the only thing there. - * Scenario is hash hit or interesting item hit. - * + * Returns the first non-Blackboard Artifact from a Node. Needed for (at + * least) Hex and Strings that want to view all types of content (not just + * AbstractFile), but don't want to display an artifact unless that's the + * only thing there. Scenario is hash hit or interesting item hit. + * * @param node Node passed into content viewer + * * @return highest priority content or null if there is no content */ public static Content getDefaultContent(Node node) { Content bbContentSeen = null; - for (Content content : (node).getLookup().lookupAll(Content.class)) { + for (Content content : (node).getLookup().lookupAll(Content.class)) { if (content instanceof BlackboardArtifact) { bbContentSeen = content; - } - else { + } else { return content; } } return bbContentSeen; } + } From e9890a72679597d1c8bb0b3a68a2e59345839f42 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 14:42:15 -0400 Subject: [PATCH 10/18] Cleaned up line queries and tested for regressions --- InternalPythonModules/android/line.py | 85 ++++++++++----------------- 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/InternalPythonModules/android/line.py b/InternalPythonModules/android/line.py index ab8e24c9f9..6edcf158fa 100644 --- a/InternalPythonModules/android/line.py +++ b/InternalPythonModules/android/line.py @@ -236,28 +236,23 @@ class LineCallLogsParser(TskCallLogsParser): def __init__(self, calllog_db): super(LineCallLogsParser, self).__init__(calllog_db.runQuery( """ - SELECT Substr(CH.call_type, -1) AS direction, - CH.start_time AS start_time, - CH.end_time AS end_time, - contacts_list_with_groups.members AS group_members, - contacts_list_with_groups.member_names AS names, - CH.caller_mid, - CH.voip_type AS call_type, - CH.voip_gc_media_type AS group_call_type + SELECT Substr(calls.call_type, -1) AS direction, + calls.start_time AS start_time, + calls.end_time AS end_time, + contact_book_w_groups.members AS group_members, + calls.caller_mid, + calls.voip_type AS call_type, + calls.voip_gc_media_type AS group_call_type FROM (SELECT id, - Group_concat(M.m_id) AS members, - Group_concat(Replace(C.server_name, ",", "")) AS member_names + Group_concat(M.m_id) AS members FROM membership AS M - JOIN naver.contacts AS C - ON M.m_id = C.m_id GROUP BY id UNION SELECT m_id, - NULL, - server_name - FROM naver.contacts) AS contacts_list_with_groups - JOIN call_history AS CH - ON CH.caller_mid = contacts_list_with_groups.id + NULL + FROM naver.contacts) AS contact_book_w_groups + JOIN call_history AS calls + ON calls.caller_mid = contact_book_w_groups.id """ ) ) @@ -355,43 +350,25 @@ class LineMessagesParser(TskMessagesParser): def __init__(self, message_db): super(LineMessagesParser, self).__init__(message_db.runQuery( """ - SELECT contact_list_with_groups.name, - contact_list_with_groups.id, - contact_list_with_groups.members, - contact_list_with_groups.member_names, - CH.from_mid, - C.server_name AS from_name, - CH.content, - CH.created_time, - CH.attachement_type, - CH.attachement_local_uri, - CH.status - FROM (SELECT G.name, - group_members.id, - group_members.members, - group_members.member_names - FROM (SELECT id, - group_concat(M.m_id) AS members, - group_concat(replace(C.server_name, - ",", - "")) as member_names - FROM membership AS M - JOIN contacts as C - ON M.m_id = C.m_id - GROUP BY id) AS group_members - JOIN groups AS G - ON G.id = group_members.id - UNION - SELECT server_name, - m_id, - NULL, - NULL - FROM contacts) AS contact_list_with_groups - JOIN chat_history AS CH - ON CH.chat_id = contact_list_with_groups.id - LEFT JOIN contacts as C - ON C.m_id = CH.from_mid - WHERE attachement_type != 6 + SELECT contact_book_w_groups.id, + contact_book_w_groups.members, + messages.from_mid, + messages.content, + messages.created_time, + messages.attachement_type, + messages.attachement_local_uri, + messages.status + FROM (SELECT id, + Group_concat(M.m_id) AS members + FROM membership AS M + GROUP BY id + UNION + SELECT m_id, + NULL + FROM contacts) AS contact_book_w_groups + JOIN chat_history AS messages + ON messages.chat_id = contact_book_w_groups.id + WHERE attachement_type != 6 """ ) ) From 77aa843d862c55c861d41cd77ecd5580be71e839 Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 22 Oct 2019 14:43:30 -0400 Subject: [PATCH 11/18] Added mechanism to get the startup window and to use it or the multi user case dialog as the parent of the file chooser. --- .../autopsy/casemodule/CaseOpenAction.java | 14 ++++++++------ .../autopsy/casemodule/StartupWindowProvider.java | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index b68d1a49f6..0ba92c7bce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Component; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -93,7 +94,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * If the open multi user case dialog is open make sure it's not set * to always be on top as this hides the file chooser on macOS. */ - OpenMultiUserCaseDialog.getInstance().setAlwaysOnTop(false); + OpenMultiUserCaseDialog multiUserCaseDialog = OpenMultiUserCaseDialog.getInstance(); + multiUserCaseDialog.setAlwaysOnTop(false); String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { @@ -102,11 +104,11 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * file (.aut file). */ /** - * Passing the fileChooser as its own parent gets around an issue - * where the fileChooser was hidden behind the CueBannerPanel ("Welcome" dialog) - * on macOS. + * The parent of the fileChooser will either be the multi user + * case dialog or the startup window. */ - int retval = fileChooser.showOpenDialog(fileChooser); + int retval = fileChooser.showOpenDialog(multiUserCaseDialog.isVisible() + ? multiUserCaseDialog : (Component) StartupWindowProvider.getInstance().getStartupWindow()); if (retval == JFileChooser.APPROVE_OPTION) { /* * Close the startup window, if it is open. @@ -116,7 +118,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action /* * Close the Open Multi-User Case window, if it is open. */ - OpenMultiUserCaseDialog.getInstance().setVisible(false); + multiUserCaseDialog.setVisible(false); /* * Try to open the case associated with the case metadata file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java index 13aae1c0de..8bec55f53c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java @@ -144,4 +144,13 @@ public class StartupWindowProvider implements StartupWindowInterface { startupWindowToUse.close(); } } + + /** + * Get the chosen startup window. + * + * @return The startup window. + */ + public StartupWindowInterface getStartupWindow() { + return startupWindowToUse; + } } From 7e745617c0cb5009ad8e50d6053e3e7bc6b02a9b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 15:38:16 -0400 Subject: [PATCH 12/18] Simplifed and tested the textnow queries --- InternalPythonModules/android/textnow.py | 78 +++++++++++------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/InternalPythonModules/android/textnow.py b/InternalPythonModules/android/textnow.py index ad9704cece..1890c7ae42 100644 --- a/InternalPythonModules/android/textnow.py +++ b/InternalPythonModules/android/textnow.py @@ -290,53 +290,50 @@ class TextNowMessagesParser(TskMessagesParser): """ super(TextNowMessagesParser, self).__init__(message_db.runQuery( """ - - SELECT CASE - WHEN message_direction == 2 THEN "" - WHEN to_addresses IS NULL THEN M.contact_value - ELSE contact_name - end from_address, - CASE - WHEN message_direction == 1 THEN "" - WHEN to_addresses IS NULL THEN M.contact_value - ELSE to_addresses - end to_address, - message_direction, - message_text, - M.READ, - M.date, - M.attach, - thread_id - FROM (SELECT group_info.contact_value, - group_info.to_addresses, - G.contact_value AS thread_id - FROM (SELECT GM.contact_value, - Group_concat(GM.member_contact_value) AS to_addresses - FROM group_members AS GM - GROUP BY GM.contact_value) AS group_info - JOIN groups AS G - ON G.contact_value = group_info.contact_value - UNION - SELECT c.contact_value, - NULL, - "-1" - FROM contacts AS c) AS to_from_map - JOIN messages AS M - ON M.contact_value = to_from_map.contact_value - WHERE message_type NOT IN ( 102, 100 ) + SELECT CASE + WHEN messages.message_direction == 2 THEN NULL + WHEN contact_book_w_groups.to_addresses IS NULL THEN + messages.contact_value + END from_address, + CASE + WHEN messages.message_direction == 1 THEN NULL + WHEN contact_book_w_groups.to_addresses IS NULL THEN + messages.contact_value + ELSE contact_book_w_groups.to_addresses + END to_address, + messages.message_direction, + messages.message_text, + messages.READ, + messages.DATE, + messages.attach, + thread_id + FROM (SELECT GM.contact_value, + Group_concat(GM.member_contact_value) AS to_addresses, + G.contact_value AS thread_id + FROM group_members AS GM + join GROUPS AS G + ON G.contact_value = GM.contact_value + GROUP BY GM.contact_value + UNION + SELECT contact_value, + NULL, + NULL + FROM contacts) AS contact_book_w_groups + join messages + ON messages.contact_value = contact_book_w_groups.contact_value + WHERE message_type NOT IN ( 102, 100 ) """ ) ) self._TEXTNOW_MESSAGE_TYPE = "TextNow Message" self._INCOMING_MESSAGE_TYPE = 1 self._OUTGOING_MESSAGE_TYPE = 2 - self._UNKNOWN_THREAD_ID = "-1" def get_message_type(self): return self._TEXTNOW_MESSAGE_TYPE def get_phone_number_from(self): - if self.result_set.getString("from_address") == "": + if self.result_set.getString("from_address") is None: return super(TextNowMessagesParser, self).get_phone_number_from() return self.result_set.getString("from_address") @@ -347,10 +344,9 @@ class TextNowMessagesParser(TskMessagesParser): return self.OUTGOING def get_phone_number_to(self): - if self.result_set.getString("to_address") == "": + if self.result_set.getString("to_address") is None: return super(TextNowMessagesParser, self).get_phone_number_to() - recipients = self.result_set.getString("to_address").split(",") - return recipients + return self.result_set.getString("to_address").split(",") def get_message_date_time(self): #convert ms to s @@ -359,7 +355,7 @@ class TextNowMessagesParser(TskMessagesParser): def get_message_read_status(self): read = self.result_set.getBoolean("read") if self.get_message_direction() == self.INCOMING: - if read == True: + if read: return self.READ return self.UNREAD @@ -375,6 +371,6 @@ class TextNowMessagesParser(TskMessagesParser): def get_thread_id(self): thread_id = self.result_set.getString("thread_id") - if thread_id == self._UNKNOWN_THREAD_ID: + if thread_id is None: return super(TextNowMessagesParser, self).get_thread_id() return thread_id From b46fe4d52432ed47e77589cccee69df604c2b1e2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 16:56:54 -0400 Subject: [PATCH 13/18] Cleaned up the whatsapp query and tested changes --- InternalPythonModules/android/whatsapp.py | 49 +++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py index 9cb7ea3d73..438784f3e2 100644 --- a/InternalPythonModules/android/whatsapp.py +++ b/InternalPythonModules/android/whatsapp.py @@ -433,31 +433,28 @@ class WhatsAppMessagesParser(TskMessagesParser): def __init__(self, message_db): super(WhatsAppMessagesParser, self).__init__(message_db.runQuery( """ - SELECT M.key_remote_jid AS id, - contact_info.recipients, - key_from_me AS direction, - CASE - WHEN M.data IS NULL THEN "" - ELSE M.data - END AS content, - M.timestamp AS send_timestamp, - M.received_timestamp, - M.remote_resource AS group_sender, - M.media_url As attachment - FROM (SELECT jid, - recipients - FROM wadb.wa_contacts AS WC - LEFT JOIN (SELECT gjid, - group_concat(CASE - WHEN jid == "" THEN NULL - ELSE jid - END) AS recipients - FROM group_participants - GROUP BY gjid) AS group_map - ON WC.jid = group_map.gjid - GROUP BY jid) AS contact_info - JOIN messages AS M - ON M.key_remote_jid = contact_info.jid + SELECT messages.key_remote_jid AS id, + contact_book_w_groups.recipients, + key_from_me AS direction, + messages.data AS content, + messages.timestamp AS send_timestamp, + messages.received_timestamp, + messages.remote_resource AS group_sender, + messages.media_url AS attachment + FROM (SELECT jid, + recipients + FROM wadb.wa_contacts AS contacts + left join (SELECT gjid, + Group_concat(CASE + WHEN jid == "" THEN NULL + ELSE jid + END) AS recipients + FROM group_participants + GROUP BY gjid) AS groups + ON contacts.jid = groups.gjid + GROUP BY jid) AS contact_book_w_groups + join messages + ON messages.key_remote_jid = contact_book_w_groups.jid """ ) ) @@ -503,6 +500,8 @@ class WhatsAppMessagesParser(TskMessagesParser): def get_message_text(self): message = self.result_set.getString("content") + if message is None: + message = super(WhatsAppMessagesParser, self).get_message_text() attachment = self.result_set.getString("attachment") if attachment is not None: return general.appendAttachmentList(message, [attachment]) From a58de8029febce29f4f454a79d0dc0fe6f7811aa Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 17:28:13 -0400 Subject: [PATCH 14/18] Cleaned up skype queries and tested them --- InternalPythonModules/android/skype.py | 141 ++++++++----------------- 1 file changed, 45 insertions(+), 96 deletions(-) diff --git a/InternalPythonModules/android/skype.py b/InternalPythonModules/android/skype.py index 5044898d5f..d8b79ac7fe 100644 --- a/InternalPythonModules/android/skype.py +++ b/InternalPythonModules/android/skype.py @@ -76,11 +76,8 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer): as they would be excluded in the join. Since the chatItem table stores both the group id or skype_id in one column, an implementation decision was made to union the person and particiapnt table together so that all rows are matched in one join - with chatItem. This result is consistently labeled contact_list_with_groups in the + with chatItem. This result is consistently labeled contact_book_w_groups in the following queries. - - In order to keep the formatting of the name consistent throughout each query, - a _format_user_name() function was created to encapsulate the CASE statement - that was being shared across them. Refer to the method for more details. """ def __init__(self): @@ -93,7 +90,12 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer): account_query_result = skype_db.runQuery( """ SELECT entry_id, - """+_format_user_name()+""" AS name + CASE + WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id + WHEN first_name is NULL THEN replace(last_name, ",", "") + WHEN last_name is NULL THEN replace(first_name, ",", "") + ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") + END AS name FROM user """ ) @@ -251,14 +253,6 @@ class SkypeCallLogsParser(TskCallLogsParser): def __init__(self, calllog_db): """ - Big picture: - The query below creates a contacts_list_with_groups table, which - represents the recipient info. A chatItem record holds ids for - both the recipient and sender. The first join onto chatItem fills - in the blanks for the recipients. The second join back onto person - handles the sender info. The result is a table with all of the - communication details. - Implementation details: - message_type w/ value 3 appeared to be the call type, regardless of if it was audio or video. @@ -266,37 +260,23 @@ class SkypeCallLogsParser(TskCallLogsParser): """ super(SkypeCallLogsParser, self).__init__(calllog_db.runQuery( """ - SELECT contacts_list_with_groups.conversation_id, - contacts_list_with_groups.participant_ids, - contacts_list_with_groups.participants, - time, - duration, - is_sender_me, - person_id as sender_id, - sender_name.name as sender_name + SELECT contact_book_w_groups.conversation_id, + contact_book_w_groups.participant_ids, + messages.time, + messages.duration, + messages.is_sender_me, + messages.person_id AS sender_id FROM (SELECT conversation_id, - Group_concat(person_id) AS participant_ids, - Group_concat("""+_format_user_name()+""") AS participants - FROM particiapnt AS PART - JOIN person AS P - ON PART.person_id = P.entry_id + Group_concat(person_id) AS participant_ids + FROM particiapnt GROUP BY conversation_id UNION - SELECT entry_id, - NULL, - """+_format_user_name()+""" AS participant - FROM person) AS contacts_list_with_groups - JOIN chatitem AS C - ON C.conversation_link = contacts_list_with_groups.conversation_id - JOIN (SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM person - UNION - SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM user) AS sender_name - ON sender_name.id = C.person_id - WHERE message_type == 3 + SELECT entry_id AS conversation_id, + NULL + FROM person) AS contact_book_w_groups + join chatitem AS messages + ON messages.conversation_link = contact_book_w_groups.conversation_id + WHERE message_type == 3 """ ) ) @@ -347,7 +327,12 @@ class SkypeContactsParser(TskContactsParser): super(SkypeContactsParser, self).__init__(contact_db.runQuery( """ SELECT entry_id, - """+_format_user_name()+""" AS name + CASE + WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id + WHEN first_name is NULL THEN replace(last_name, ",", "") + WHEN last_name is NULL THEN replace(first_name, ",", "") + ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") + END AS name FROM person """ ) @@ -379,39 +364,25 @@ class SkypeMessagesParser(TskMessagesParser): """ super(SkypeMessagesParser, self).__init__(message_db.runQuery( """ - SELECT contacts_list_with_groups.conversation_id, - contacts_list_with_groups.participant_ids, - contacts_list_with_groups.participants, - time, - content, - device_gallery_path, - is_sender_me, - person_id as sender_id, - sender_name.name AS sender_name - FROM (SELECT conversation_id, - Group_concat(person_id) AS participant_ids, - Group_concat("""+_format_user_name()+""") AS participants - FROM particiapnt AS PART - JOIN person AS P - ON PART.person_id = P.entry_id - GROUP BY conversation_id - UNION - SELECT entry_id as conversation_id, - NULL, - """+_format_user_name()+""" AS participant - FROM person) AS contacts_list_with_groups - JOIN chatitem AS C - ON C.conversation_link = contacts_list_with_groups.conversation_id - JOIN (SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM person - UNION - SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM user) AS sender_name - ON sender_name.id = C.person_id + SELECT contact_book_w_groups.conversation_id, + contact_book_w_groups.participant_ids, + messages.time, + messages.content, + messages.device_gallery_path, + messages.is_sender_me, + messages.person_id as sender_id + FROM (SELECT conversation_id, + Group_concat(person_id) AS participant_ids + FROM particiapnt + GROUP BY conversation_id + UNION + SELECT entry_id as conversation_id, + NULL + FROM person) AS contact_book_w_groups + JOIN chatitem AS messages + ON messages.conversation_link = contact_book_w_groups.conversation_id WHERE message_type != 3 - """ + """ ) ) self._SKYPE_MESSAGE_TYPE = "Skype Message" @@ -469,25 +440,3 @@ class SkypeMessagesParser(TskMessagesParser): if group_ids is not None: return self.result_set.getString("conversation_id") return super(SkypeMessagesParser, self).get_thread_id() - -def _format_user_name(): - """ - This CASE SQL statement is used in many queries to - format the names of users. For a user, there is a first_name - column and a last_name column. Some of these columns can be null - and our goal is to produce the cleanest data possible. In the event - that both the first and last name columns are null, we return the skype_id - which is stored in the database as 'entry_id'. Commas are removed from the name - so that we can concatenate names into a comma seperate list for group chats. - """ - - return """ - CASE - WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id - WHEN first_name is NULL THEN replace(last_name, ",", "") - WHEN last_name is NULL THEN replace(first_name, ",", "") - ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") - END - """ - - From 0de0c7d852bae5dfba0e0a085e494202a458e62b Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 22 Oct 2019 17:53:07 -0400 Subject: [PATCH 15/18] Add note about location of Options dialog on Mac. --- docs/doxygen-user/main.dox | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index 122e14fb0f..61d555ace4 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -6,6 +6,8 @@ Overview This is the User's Guide for the open source Autopsy platform. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The developer's guide will help you develop your own Autopsy modules. +Note: For those users running Autopsy on Mac devices, the functionality available through the "Tools" -> "Options" dialog as described in this documentation can be accessed through the system menu bar under "Preferences" or through the Cmd + , (command-comma) shortcut. + Help Topics ------- The following topics are available here: From 08ccb2a9c95dd0ff1decac9124318ab8a5fce53a Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 23 Oct 2019 11:39:13 -0400 Subject: [PATCH 16/18] Update EmailMessage.java If messageid is null then set it to "" in setMessageID --- .../thunderbirdparser/EmailMessage.java | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java index 40f2fc0933..09a6637e6e 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java @@ -83,7 +83,7 @@ class EmailMessage { void setSubject(String subject) { if (subject != null) { this.subject = subject; - if(subject.matches("^[R|r][E|e].*?:.*")) { + if (subject.matches("^[R|r][E|e].*?:.*")) { this.simplifiedSubject = subject.replaceAll("[R|r][E|e].*?:", "").trim(); replySubject = true; } else { @@ -93,19 +93,19 @@ class EmailMessage { this.simplifiedSubject = ""; } } - + /** * Returns the orginal subject with the "RE:" stripped off". - * + * * @return Message subject with the "RE" stripped off */ String getSimplifiedSubject() { return simplifiedSubject; } - + /** * Returns whether or not the message subject started with "RE:" - * + * * @return true if the original subject started with RE otherwise false. */ boolean isReplySubject() { @@ -121,6 +121,7 @@ class EmailMessage { this.headers = headers; } } + String getTextBody() { return textBody; } @@ -211,75 +212,80 @@ class EmailMessage { this.localPath = localPath; } } - + /** - * Returns the value of the Message-ID header field of this message or - * empty string if it is not present. - * + * Returns the value of the Message-ID header field of this message or empty + * string if it is not present. + * * @return the identifier of this message. */ String getMessageID() { return messageID; } - + /** * Sets the identifier of this message. - * + * * @param messageID identifer of this message */ void setMessageID(String messageID) { - this.messageID = messageID; + if (messageID != null) { + this.messageID = messageID; + } else { + this.messageID = ""; + } } - + /** - * Returns the messageID of the parent message or empty String if not present. - * + * Returns the messageID of the parent message or empty String if not + * present. + * * @return the idenifier of the message parent */ String getInReplyToID() { return inReplyToID; } - + /** * Sets the messageID of the parent message. - * + * * @param inReplyToID messageID of the parent message. */ void setInReplyToID(String inReplyToID) { this.inReplyToID = inReplyToID; } - + /** - * Returns a list of Message-IDs listing the parent, grandparent, - * great-grandparent, and so on, of this message. - * + * Returns a list of Message-IDs listing the parent, grandparent, + * great-grandparent, and so on, of this message. + * * @return The reference list or empty string if none is available. */ List getReferences() { return references; } - + /** * Set the list of reference message-IDs from the email message header. - * - * @param references + * + * @param references */ void setReferences(List references) { this.references = references; } - + /** * Sets the ThreadID of this message. - * + * * @param threadID - the thread ID to set */ void setMessageThreadID(String threadID) { this.messageThreadID = threadID; } - + /** * Returns the ThreadID for this message. - * + * * @return - the message thread ID or "" is non is available */ String getMessageThreadID() { @@ -308,7 +314,7 @@ class EmailMessage { private long aTime = 0L; private long mTime = 0L; - + private TskData.EncodingType encodingType = TskData.EncodingType.NONE; String getName() { @@ -394,14 +400,14 @@ class EmailMessage { this.mTime = mTime.getTime() / 1000; } } - - void setEncodingType(TskData.EncodingType encodingType){ + + void setEncodingType(TskData.EncodingType encodingType) { this.encodingType = encodingType; } - - TskData.EncodingType getEncodingType(){ + + TskData.EncodingType getEncodingType() { return encodingType; } - + } } From 0b8ea73f7f52f3968a90aaab94b7a7c0a6cefd98 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 23 Oct 2019 11:45:25 -0400 Subject: [PATCH 17/18] Update ThunderbirdMboxFileIngestModule.java Revert changes from prior commit. --- .../ThunderbirdMboxFileIngestModule.java | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 91bf391e63..5c42269a8a 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -65,13 +65,12 @@ import org.sleuthkit.datamodel.TskException; * structure and metadata. */ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { - private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private FileManager fileManager; private IngestJobContext context; private Blackboard blackboard; - + private Case currentCase; /** @@ -81,7 +80,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override - @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) + @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; try { @@ -104,8 +103,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } //skip unalloc - if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) - || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) || + (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -116,7 +115,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { // check its signature boolean isMbox = false; boolean isEMLFile = false; - + try { byte[] t = new byte[64]; if (abstractFile.getSize() > 64) { @@ -133,7 +132,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isMbox) { return processMBox(abstractFile); } - + if (isEMLFile) { return processEMLFile(abstractFile); } @@ -141,7 +140,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (PstParser.isPstFile(abstractFile)) { return processPst(abstractFile); } - + if (VcardParser.isVcardFile(abstractFile)) { return processVcard(abstractFile); } @@ -161,7 +160,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -189,11 +188,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { PstParser parser = new PstParser(services); PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - switch (result) { + switch( result) { case OK: Iterator pstMsgIterator = parser.getEmailMessageIterator(); if (pstMsgIterator != null) { - processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); + processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile); } else { // sometimes parser returns ParseResult=OK but there are no messages postErrorMessage( @@ -274,7 +273,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -299,16 +298,16 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()); + MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); List emails = new ArrayList<>(); - if (emailIterator != null) { - while (emailIterator.hasNext()) { + if(emailIterator != null) { + while(emailIterator.hasNext()) { EmailMessage emailMessage = emailIterator.next(); - if (emailMessage != null) { + if(emailMessage != null) { emails.add(emailMessage); } } - + String errors = emailIterator.getErrors(); if (!errors.isEmpty()) { postErrorMessage( @@ -316,7 +315,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { abstractFile.getName()), errors); } } - processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile); + processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS @@ -324,7 +323,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - + /** * Parse and extract data from a vCard file. * @@ -348,8 +347,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } return ProcessResult.OK; } - - private ProcessResult processEMLFile(AbstractFile abstractFile) { + + private ProcessResult processEMLFile(AbstractFile abstractFile) { try { EmailMessage message = EMLParser.parse(abstractFile); @@ -401,7 +400,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a module output folder. - * + * * @throws NoCurrentCaseException if there is no open case. * * @return the module output folder @@ -436,39 +435,38 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param abstractFile */ private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { - + // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. - try { + try{ EmailMessageThreader.threadMessages(partialEmailsForThreading); - } catch (Exception ex) { + } catch(Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - + List derivedFiles = new ArrayList<>(); int msgCnt = 0; - while (fullMessageIterator.hasNext()) { + while(fullMessageIterator.hasNext()) { EmailMessage current = fullMessageIterator.next(); - - if (current == null) { + + if(current == null) { continue; } - if (partialEmailsForThreading.size() > msgCnt) { + if(partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - - if ((threaded.getMessageID() != null) && - (threaded.getMessageID().equals(current.getMessageID()) - && threaded.getSubject().equals(current.getSubject()))) { - current.setMessageThreadID(threaded.getMessageThreadID()); - } + + if(threaded.getMessageID().equals(current.getMessageID()) && + threaded.getSubject().equals(current.getSubject())) { + current.setMessageThreadID(threaded.getMessageThreadID()); + } } - + BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); - - if ((msgArtifact != null) && (current.hasAttachment())) { - derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact)); + + if ((msgArtifact != null) && (current.hasAttachment())) { + derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); } } @@ -479,7 +477,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } context.addFilesToJob(derivedFiles); } - /** * Add the given attachments as derived files and reschedule them for * ingest. @@ -520,30 +517,29 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } /** - * Finds and returns a set of unique email addresses found in the input - * string + * Finds and returns a set of unique email addresses found in the input string * * @param input - input string, like the To/CC line from an email header - * + * * @return Set: set of email addresses found in the input string */ private Set findEmailAddresess(String input) { Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", - Pattern.CASE_INSENSITIVE); + Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); Set emailAddresses = new HashSet<>(); while (m.find()) { - emailAddresses.add(m.group()); + emailAddresses.add( m.group()); } return emailAddresses; } - + /** * Add a blackboard artifact for the given e-mail message. * * @param email The e-mail message. * @param abstractFile The associated file. - * + * * @return The generated e-mail message artifact. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) @@ -567,69 +563,73 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List senderAddressList = new ArrayList<>(); String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - + AccountFileInstance senderAccountInstance = null; if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { senderAccountInstance = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS } - } else { - logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS + catch(TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS + } } - + else { + logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS + } + List recipientAddresses = new ArrayList<>(); recipientAddresses.addAll(findEmailAddresess(to)); recipientAddresses.addAll(findEmailAddresess(cc)); recipientAddresses.addAll(findEmailAddresess(bcc)); - + List recipientAccountInstances = new ArrayList<>(); recipientAddresses.forEach((addr) -> { try { - AccountFileInstance recipientAccountInstance - = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, - EmailParserModuleFactory.getModuleName(), abstractFile); + AccountFileInstance recipientAccountInstance = + currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + EmailParserModuleFactory.getModuleName(), abstractFile); recipientAccountInstances.add(recipientAccountInstance); - } catch (TskCoreException ex) { + } + catch(TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS } }); - + addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - + addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - + addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); - - addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + + addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); - - addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), + + addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), ATTRIBUTE_TYPE.TSK_PATH, bbattributes); - + addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); - + + try { - + bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); // Add account relationships - currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL); - + currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); + try { // index the artifact for keyword search - blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); @@ -640,11 +640,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return bbart; } - + /** * Add an attribute of a specified type to a supplied Collection. - * - * @param stringVal The attribute value. + * + * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. */ @@ -656,7 +656,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -666,10 +666,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); } } - + /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param longVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -679,10 +679,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); } } - + /** * Post an error message for the user. - * + * * @param subj The error subject. * @param details The error details. */ @@ -693,7 +693,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get the IngestServices object. - * + * * @return The IngestServices object. */ IngestServices getServices() { From 166366e2410e468a9070b8689f04cd00ea1ab96c Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 23 Oct 2019 14:12:52 -0400 Subject: [PATCH 18/18] Don't make building dependent on downloading test data files. Out Travis build occasionally fails when attempting to dowmload these files. Ideally these files should be downloaded only if tests are to be run that require them. --- Core/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/build.xml b/Core/build.xml index 28e64b83e5..0e5c90ef04 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -137,7 +137,7 @@ - +