From 6e3f4e77a042b4f1e904b0cd6f6ee44fb5ad0bb9 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 30 Mar 2022 10:41:27 -0400 Subject: [PATCH] fixes --- .../datamodel/DisplayableItemNodeVisitor.java | 29 +- .../autopsy/datamodel/ImageNode.java | 251 ++++++++++++++++++ .../datamodel/LocalFilesDataSourceNode.java | 94 +++++++ .../autopsy/datamodel/ViewsNode.java | 84 ------ .../autopsy/datamodel/VolumeNode.java | 237 +++++++++++++++++ .../DirectoryTreeTopComponent.java | 31 ++- .../directorytree/SelectionContext.java | 4 +- .../directorytree/ViewContextAction.java | 9 +- .../mainui/datamodel/HostPersonDAO.java | 11 +- .../mainui/datamodel/PersonSearchParams.java | 2 +- .../autopsy/mainui/nodes/RootFactory.java | 136 ++++++++-- 11 files changed, 761 insertions(+), 127 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java create mode 100755 Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java delete mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java create mode 100644 Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 72483b2d25..91ee4127e7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -48,6 +48,10 @@ public interface DisplayableItemNodeVisitor { T visit(FileNode fn); + T visit(ImageNode in); + + T visit(VolumeNode vn); + T visit(PoolNode pn); T visit(SlackFileNode sfn); @@ -56,8 +60,6 @@ public interface DisplayableItemNodeVisitor { /* * Views Area */ - T visit(ViewsNode vn); - T visit(BlackboardArtifactNode ban); T visit(CommonAttributeValueNode cavn); @@ -85,7 +87,6 @@ public interface DisplayableItemNodeVisitor { /* * Reports */ - T visit(EmptyNode.MessageNode emptyNode); /* @@ -93,12 +94,12 @@ public interface DisplayableItemNodeVisitor { */ T visit(AttachmentNode node); - /* * Unsupported node */ T visit(UnsupportedContentNode ucn); + T visit(LocalFilesDataSourceNode lfdsn); /** @@ -168,11 +169,21 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(fn); } + @Override + public T visit(ImageNode in) { + return defaultVisit(in); + } + @Override public T visit(PoolNode pn) { return defaultVisit(pn); } + @Override + public T visit(VolumeNode vn) { + return defaultVisit(vn); + } + @Override public T visit(SlackFileNode sfn) { return defaultVisit(sfn); @@ -188,11 +199,6 @@ public interface DisplayableItemNodeVisitor { return defaultVisit(ftByMimeTypeEmptyNode); } - @Override - public T visit(ViewsNode vn) { - return defaultVisit(vn); - } - @Override public T visit(LayoutFileNode lfn) { return defaultVisit(lfn); @@ -227,5 +233,10 @@ public interface DisplayableItemNodeVisitor { public T visit(UnsupportedContentNode node) { return defaultVisit(node); } + + @Override + public T visit(LocalFilesDataSourceNode node) { + return defaultVisit(node); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java new file mode 100644 index 0000000000..9761bb00e9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -0,0 +1,251 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2012-2021 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.Action; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.DeleteDataSourceAction; +import org.sleuthkit.autopsy.datasourcesummary.ui.ViewSummaryInformationAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import org.sleuthkit.autopsy.directorytree.FileSearchTreeAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.VirtualDirectory; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; +import org.sleuthkit.datamodel.Tag; + +/** + * This class is used to represent the "Node" for the image. The children of + * this node are volumes. + */ +public class ImageNode extends AbstractContentNode { + + private static final Logger logger = Logger.getLogger(ImageNode.class.getName()); + private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED); + + /** + * Helper so that the display name and the name used in building the path + * are determined the same way. + * + * @param i Image to get the name of + * + * @return short name for the Image + */ + static String nameForImage(Image i) { + return i.getName(); + } + + /** + * @param img + */ + public ImageNode(Image img) { + super(img); + + // set name, display name, and icon + String imgName = nameForImage(img); + this.setDisplayName(imgName); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/hard-drive-icon.jpg"); //NON-NLS + + // Listen for ingest events so that we can detect new added files (e.g. carved) + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); + // Listen for case events so that we can detect when case is closed + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + } + + private void removeListeners() { + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + } + + /** + * Right click action for this node + * + * @param context + * + * @return + */ + @Override + @Messages({"ImageNode.action.runIngestMods.text=Run Ingest Modules", + "ImageNode.getActions.openFileSearchByAttr.text=Open File Search by Attributes"}) + public Action[] getActions(boolean context) { + + List actionsList = new ArrayList<>(); + actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); + actionsList.add(new FileSearchTreeAction(Bundle.ImageNode_getActions_openFileSearchByAttr_text(), content.getId())); + actionsList.add(new ViewSummaryInformationAction(content.getId())); + actionsList.add(new RunIngestModulesAction(Collections.singletonList(content))); + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "ImageNode.getActions.viewInNewWin.text"), this)); + actionsList.add(new DeleteDataSourceAction(content.getId())); + actionsList.add(null); + actionsList.addAll(Arrays.asList(super.getActions(true))); + return actionsList.toArray(new Action[0]); + } + + @Override + @Messages({"ImageNode.createSheet.size.name=Size (Bytes)", + "ImageNode.createSheet.size.displayName=Size (Bytes)", + "ImageNode.createSheet.size.desc=Size of the data source in bytes.", + "ImageNode.createSheet.type.name=Type", + "ImageNode.createSheet.type.displayName=Type", + "ImageNode.createSheet.type.desc=Type of the image.", + "ImageNode.createSheet.type.text=Image", + "ImageNode.createSheet.sectorSize.name=Sector Size (Bytes)", + "ImageNode.createSheet.sectorSize.displayName=Sector Size (Bytes)", + "ImageNode.createSheet.sectorSize.desc=Sector size of the image in bytes.", + "ImageNode.createSheet.timezone.name=Timezone", + "ImageNode.createSheet.timezone.displayName=Timezone", + "ImageNode.createSheet.timezone.desc=Timezone of the image", + "ImageNode.createSheet.deviceId.name=Device ID", + "ImageNode.createSheet.deviceId.displayName=Device ID", + "ImageNode.createSheet.deviceId.desc=Device ID of the image"}) + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "ImageNode.createSheet.name.desc"), + getDisplayName())); + + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_type_name(), + Bundle.ImageNode_createSheet_type_displayName(), + Bundle.ImageNode_createSheet_type_desc(), + Bundle.ImageNode_createSheet_type_text())); + + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_size_name(), + Bundle.ImageNode_createSheet_size_displayName(), + Bundle.ImageNode_createSheet_size_desc(), + this.content.getSize())); + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_sectorSize_name(), + Bundle.ImageNode_createSheet_sectorSize_displayName(), + Bundle.ImageNode_createSheet_sectorSize_desc(), + this.content.getSsize())); + + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_timezone_name(), + Bundle.ImageNode_createSheet_timezone_displayName(), + Bundle.ImageNode_createSheet_timezone_desc(), + this.content.getTimeZone())); + + sheetSet.put(new NodeProperty<>(Bundle.ImageNode_createSheet_deviceId_name(), + Bundle.ImageNode_createSheet_deviceId_displayName(), + Bundle.ImageNode_createSheet_deviceId_desc(), + content.getDeviceId())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + /* + * This property change listener refreshes the tree when a new file is + * carved out of this image (i.e, the image is being treated as raw bytes + * and was ingested by the RawDSProcessor). + */ + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + + // See if the new file is a child of ours + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + if ((evt.getOldValue() instanceof ModuleContentEvent) == false) { + return; + } + ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue(); + if ((moduleContentEvent.getSource() instanceof Content) == false) { + return; + } + Content newContent = (Content) moduleContentEvent.getSource(); + + try { + Content parent = newContent.getParent(); + if (parent != null) { + // Is this a new carved file? + if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) { + // Is this new carved file for this data source? + if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) { + // Find the image (if any) associated with the new content and + // trigger a refresh if it matches the image wrapped by this node. + while ((parent = parent.getParent()) != null) { + if (parent.getId() == getContent().getId()) { + BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent()); + break; + } + } + } + } + } + } catch (TskCoreException ex) { + // Do nothing. + } catch (NoSuchEventBusException ex) { + logger.log(Level.WARNING, "Failed to post key refresh event.", ex); // NON-NLS + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + if (evt.getNewValue() == null) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + removeListeners(); + } + } + }; + + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java new file mode 100755 index 0000000000..b043af914c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFilesDataSourceNode.java @@ -0,0 +1,94 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.datamodel.LocalFilesDataSource; + +/** + * + * + */ +public class LocalFilesDataSourceNode extends VirtualDirectoryNode { + + private final LocalFilesDataSource localFileDataSource; + + public LocalFilesDataSourceNode(LocalFilesDataSource ld) { + super(ld); + localFileDataSource = ld; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS + } + + @Override + @NbBundle.Messages({"LocalFilesDataSourceNode.createSheet.size.name=Size (Bytes)", + "LocalFilesDataSourceNode.createSheet.size.displayName=Size (Bytes)", + "LocalFilesDataSourceNode.createSheet.size.desc=Size of the data source in bytes.", + "LocalFilesDataSourceNode.createSheet.type.name=Type", + "LocalFilesDataSourceNode.createSheet.type.displayName=Type", + "LocalFilesDataSourceNode.createSheet.type.desc=Type of the image.", + "LocalFilesDataSourceNode.createSheet.type.text=Logical File Set", + "LocalFilesDataSourceNode.createSheet.timezone.name=Timezone", + "LocalFilesDataSourceNode.createSheet.timezone.displayName=Timezone", + "LocalFilesDataSourceNode.createSheet.timezone.desc=Timezone of the image", + "LocalFilesDataSourceNode.createSheet.deviceId.name=Device ID", + "LocalFilesDataSourceNode.createSheet.deviceId.displayName=Device ID", + "LocalFilesDataSourceNode.createSheet.deviceId.desc=Device ID of the image", + "LocalFilesDataSourceNode.createSheet.name.name=Name", + "LocalFilesDataSourceNode.createSheet.name.displayName=Name", + "LocalFilesDataSourceNode.createSheet.name.desc=no description", + "LocalFilesDataSourceNode.createSheet.noDesc=no description",}) + protected Sheet createSheet() { + Sheet sheet = new Sheet(); + Sheet.Set sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + + sheetSet.put(new NodeProperty<>(Bundle.LocalFilesDataSourceNode_createSheet_name_name(), + Bundle.LocalFilesDataSourceNode_createSheet_name_displayName(), + Bundle.LocalFilesDataSourceNode_createSheet_name_desc(), + getName())); + + sheetSet.put(new NodeProperty<>(Bundle.LocalFilesDataSourceNode_createSheet_type_name(), + Bundle.LocalFilesDataSourceNode_createSheet_type_displayName(), + Bundle.LocalFilesDataSourceNode_createSheet_type_desc(), + Bundle.LocalFilesDataSourceNode_createSheet_type_text())); + + sheetSet.put(new NodeProperty<>(Bundle.LocalFilesDataSourceNode_createSheet_size_name(), + Bundle.LocalFilesDataSourceNode_createSheet_size_displayName(), + Bundle.LocalFilesDataSourceNode_createSheet_size_desc(), + this.content.getSize())); + + sheetSet.put(new NodeProperty<>(Bundle.LocalFilesDataSourceNode_createSheet_timezone_name(), + Bundle.LocalFilesDataSourceNode_createSheet_timezone_displayName(), + Bundle.LocalFilesDataSourceNode_createSheet_timezone_desc(), + "")); + + sheetSet.put(new NodeProperty<>(Bundle.LocalFilesDataSourceNode_createSheet_deviceId_name(), + Bundle.LocalFilesDataSourceNode_createSheet_deviceId_displayName(), + Bundle.LocalFilesDataSourceNode_createSheet_deviceId_desc(), + localFileDataSource.getDeviceId())); + + return sheet; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java deleted file mode 100644 index 396b5818de..0000000000 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2011-2018 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. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.datamodel; - -import org.openide.nodes.Node; -import org.openide.nodes.Sheet; -import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; -import org.sleuthkit.autopsy.mainui.nodes.ViewsTypeFactory.ViewsChildren; - -/** - * - * Node for the views - * - */ -public class ViewsNode extends DisplayableItemNode { - - public static final String NAME = NbBundle.getMessage(ViewsNode.class, "ViewsNode.name.text"); - private final long dsObjId; - - public ViewsNode() { - this(0); - } - - public ViewsNode(long dsObjId) { - - super(new ViewsChildren(dsObjId > 0 ? dsObjId : null), Lookups.singleton(NAME)); - setName(NAME); - setDisplayName(NAME); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/views.png"); //NON-NLS - this.dsObjId = dsObjId; - } - - public Node clone() { - return new ViewsNode(dsObjId); - } - - @Override - public boolean isLeafTypeNode() { - return false; - } - - @Override - public T accept(DisplayableItemNodeVisitor visitor) { - return visitor.visit(this); - } - - @Override - protected Sheet createSheet() { - Sheet sheet = super.createSheet(); - Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); - if (sheetSet == null) { - sheetSet = Sheet.createPropertiesSet(); - sheet.put(sheetSet); - } - - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.name"), - NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.displayName"), - NbBundle.getMessage(this.getClass(), "ViewsNode.createSheet.name.desc"), - NAME)); - return sheet; - } - - @Override - public String getItemType() { - return getClass().getName(); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java new file mode 100644 index 0000000000..a4267a3a6d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -0,0 +1,237 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2021 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javax.swing.Action; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.BaseChildFactory.NoSuchEventBusException; +import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.ModuleContentEvent; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Pool; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.VirtualDirectory; +import org.sleuthkit.datamodel.Volume; +import org.sleuthkit.autopsy.directorytree.FileSystemDetailsAction; +import org.sleuthkit.datamodel.Tag; + +/** + * This class is used to represent the "Node" for the volume. Its child is the + * root directory of a file system + */ +public class VolumeNode extends AbstractContentNode { + + private static final Logger logger = Logger.getLogger(VolumeNode.class.getName()); + private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestModuleEvent.CONTENT_CHANGED); + + /** + * Helper so that the display name and the name used in building the path + * are determined the same way. + * + * @param vol Volume to get the name of + * + * @return short name for the Volume + */ + static String nameForVolume(Volume vol) { + return "vol" + Long.toString(vol.getAddr()); //NON-NLS + } + + /** + * + * @param vol underlying Content instance + */ + public VolumeNode(Volume vol) { + super(vol); + + // set name, display name, and icon + String volName = nameForVolume(vol); + long end = vol.getStart() + (vol.getLength() - 1); + String tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + "-" + end + ")"; + + // If this is a pool volume use a custom display name + try { + if (vol.getParent() != null + && vol.getParent().getParent() instanceof Pool) { + // Pool volumes are not contiguous so printing a range of blocks is inaccurate + tempVolName = volName + " (" + vol.getDescription() + ": " + vol.getStart() + ")"; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error looking up parent(s) of volume with obj ID = " + vol.getId(), ex); + } + this.setDisplayName(tempVolName); + + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/vol-icon.png"); //NON-NLS + // Listen for ingest events so that we can detect new added files (e.g. carved) + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, pcl); + // Listen for case events so that we can detect when case is closed + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + } + + private void removeListeners() { + IngestManager.getInstance().removeIngestModuleEventListener(pcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), pcl); + } + + /* + * This property change listener refreshes the tree when a new file is + * carved out of the unallocated space of this volume. + */ + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { + String eventType = evt.getPropertyName(); + + // See if the new file is a child of ours + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) { + if ((evt.getOldValue() instanceof ModuleContentEvent) == false) { + return; + } + ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue(); + if ((moduleContentEvent.getSource() instanceof Content) == false) { + return; + } + Content newContent = (Content) moduleContentEvent.getSource(); + + try { + Content parent = newContent.getParent(); + if (parent != null) { + // Is this a new carved file? + if (parent.getName().equals(VirtualDirectory.NAME_CARVED)) { + // Is this new carved file for this data source? + if (newContent.getDataSource().getId() == getContent().getDataSource().getId()) { + // Find the volume (if any) associated with the new content and + // trigger a refresh if it matches the volume wrapped by this node. + while ((parent = parent.getParent()) != null) { + if (parent.getId() == getContent().getId()) { + BaseChildFactory.post(getName(), new BaseChildFactory.RefreshKeysEvent()); + break; + } + } + } + } + } + } catch (TskCoreException ex) { + // Do nothing. + } catch (NoSuchEventBusException ex) { + logger.log(Level.WARNING, eventType, ex); + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + if (evt.getNewValue() == null) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + removeListeners(); + } + } + }; + + /** + * Right click action for volume node + * + * @param popup + * + * @return + */ + @Override + public Action[] getActions(boolean popup) { + List actionsList = new ArrayList<>(); + actionsList.add(new FileSystemDetailsAction(content)); + actionsList.add(new NewWindowViewAction( + NbBundle.getMessage(this.getClass(), "VolumeNode.getActions.viewInNewWin.text"), this)); + actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); + actionsList.add(null); + actionsList.addAll(Arrays.asList(super.getActions(true))); + + return actionsList.toArray(new Action[actionsList.size()]); + } + + @Override + protected Sheet createSheet() { + Sheet sheet = super.createSheet(); + Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); + if (sheetSet == null) { + sheetSet = Sheet.createPropertiesSet(); + sheet.put(sheetSet); + } + + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.name.desc"), + this.getDisplayName())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.id.desc"), + content.getAddr())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.startSector.desc"), + content.getStart())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.lenSectors.desc"), + content.getLength())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.description.desc"), + content.getDescription())); + sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.name"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.displayName"), + NbBundle.getMessage(this.getClass(), "VolumeNode.createSheet.flags.desc"), + content.getFlagsAsString())); + + return sheet; + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String getItemType() { + return DisplayableItemNode.FILE_PARENT_NODE_KEY; + } + + /** + * Reads and returns a list of all tags associated with this content node. + * + * Null implementation of an abstract method. + * + * @return list of tags associated with the node. + */ + @Override + protected List getAllTagsFromDatabase() { + return new ArrayList<>(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 39de4326f4..17dc2760ed 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -77,7 +77,6 @@ import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.ViewsNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.corecomponents.SelectionResponder; import org.sleuthkit.autopsy.coreutils.ThreadConfined; @@ -87,6 +86,11 @@ import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory.KeywordSetFactory; import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.BlackboardArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.mainui.nodes.RootFactory; +import org.sleuthkit.autopsy.mainui.nodes.RootFactory.AnalysisResultsRootNode; +import org.sleuthkit.autopsy.mainui.nodes.RootFactory.DataArtifactsRootNode; +import org.sleuthkit.autopsy.mainui.nodes.RootFactory.OsAccountsRootNode; +import org.sleuthkit.autopsy.mainui.nodes.RootFactory.PersonNode; +import org.sleuthkit.autopsy.mainui.nodes.RootFactory.ViewsRootNode; import org.sleuthkit.autopsy.mainui.nodes.TreeNode; import org.sleuthkit.autopsy.mainui.nodes.ViewsTypeFactory.MimeParentNode; import org.sleuthkit.datamodel.Account; @@ -126,7 +130,10 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private static final String SETTINGS_FILE = "CasePreferences.properties"; //NON-NLS // nodes to be opened if present at top level - private static final Set NODES_TO_EXPAND = Stream.of(AnalysisResults.getName(), DataArtifacts.getName(), ViewsNode.NAME) + private static final Set NODES_TO_EXPAND_PREFIXES = Stream.of( + AnalysisResultsRootNode.getNamePrefix(), + DataArtifactsRootNode.getNamePrefix(), + ViewsRootNode.getNamePrefix()) .collect(Collectors.toSet()); /** @@ -195,7 +202,15 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat .forEach(tree::expandNode); } else { Stream.of(rootChildrenNodes) - .filter(n -> n != null && NODES_TO_EXPAND.contains(n.getName())) + .filter(n -> { + // find any where node name is present in prefixes + return n != null + && n.getName() != null + && NODES_TO_EXPAND_PREFIXES.stream() + .filter(prefix -> n.getName().startsWith(prefix)) + .findAny() + .isPresent(); + }) .forEach(tree::expandNode); } } @@ -1134,9 +1149,13 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private Optional getCategoryNodeChild(Children children, Category category) { switch (category) { case DATA_ARTIFACT: - return Optional.ofNullable(children.findChild(DataArtifacts.getName())); + return Stream.of(children.getNodes(true)) + .filter(n -> n.getName() != null && n.getName().startsWith(DataArtifactsRootNode.getNamePrefix())) + .findFirst(); case ANALYSIS_RESULT: - return Optional.ofNullable(children.findChild(AnalysisResults.getName())); + return Stream.of(children.getNodes(true)) + .filter(n -> n.getName() != null && n.getName().startsWith(AnalysisResultsRootNode.getNamePrefix())) + .findFirst(); default: LOGGER.log(Level.WARNING, "Unbale to find category of type: " + category.name()); return Optional.empty(); @@ -1246,7 +1265,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } - if (OsAccounts.getListName().equals(node.getName())) { + if (node.getName() != null && node.getName().startsWith(OsAccountsRootNode.getNamePrefix())) { return Optional.of(node); } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java index fa798a830a..6185ad9d5a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java @@ -67,7 +67,9 @@ enum SelectionContext { // Parent of root node or root node. Occurs during case open / close. return SelectionContext.OTHER; } else if ((!Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) && AllDataSourcesNode.getNameIdentifier().equals(n.getParentNode().getName())) - || (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) && DataSourceFilesNode.getNameIdentifier().equals(n.getParentNode().getName()))) { + || (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true) + && n.getParentNode().getName() != null + && n.getParentNode().getName().startsWith(DataSourceFilesNode.getNamePrefix()))) { // if group by data type and root is the DataSourcesNode or // if group by persons/hosts and parent of DataSourceFilesNode // then it is a data source node diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 533696cb32..1cd56d46f7 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -299,7 +299,14 @@ public class ViewContextAction extends AbstractAction { } // for this data source, get the "Data Sources" child node - Node datasourceGroupingNode = treeNode.getChildren().findChild(DataSourceFilesNode.getNameIdentifier()); + Node datasourceGroupingNode = Stream.of(treeNode.getChildren().getNodes(true)) + .filter(nd -> nd != null && nd.getName() != null && nd.getName().startsWith(DataSourceFilesNode.getNamePrefix())) + .findAny() + .orElse(null); + + if (datasourceGroupingNode != null) { + continue; + } // check whether this is the data source we are looking for Node parentTreeViewNode = findParentNodeInTree(parentContent, datasourceGroupingNode); diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java index fba0cf396b..367ed40b3e 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/HostPersonDAO.java @@ -26,7 +26,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.mainui.datamodel.TreeResultsDTO.TreeDisplayCount; @@ -41,8 +41,9 @@ import org.sleuthkit.datamodel.TskCoreException; /** * - * Dao for hosts. + * Dao for hosts and persons. */ +@Messages({"HostPersonDAO_unknownPersons_displayName=Unknown Persons"}) public class HostPersonDAO extends AbstractDAO { private static HostPersonDAO instance = null; @@ -53,6 +54,10 @@ public class HostPersonDAO extends AbstractDAO { } return instance; } + + public static String getUnknownPersonsName() { + return Bundle.HostPersonDAO_unknownPersons_displayName(); + } private SleuthkitCase getCase() throws NoCurrentCaseException { return Case.getCurrentCaseThrows().getSleuthkitCase(); @@ -110,7 +115,7 @@ public class HostPersonDAO extends AbstractDAO { TreeDisplayCount.NOT_SHOWN); } - @NbBundle.Messages({"HostPersonDAO_unknownPersons_displayName=Unknown Persons"}) + private TreeItemDTO createPersonTreeItem(Person person) { return new TreeItemDTO<>( PersonSearchParams.getTypeId(), diff --git a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java index 94d030ae17..8c882a80c8 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/datamodel/PersonSearchParams.java @@ -25,7 +25,7 @@ import org.sleuthkit.datamodel.Person; * Search parameters for a given person. */ public class PersonSearchParams { - private static final String TYPE_ID = "Host"; + private static final String TYPE_ID = "Person"; public static String getTypeId() { return TYPE_ID; diff --git a/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java b/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java index b9eed50ef9..0677f5c563 100644 --- a/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java +++ b/Core/src/org/sleuthkit/autopsy/mainui/nodes/RootFactory.java @@ -31,9 +31,11 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle.Messages; import org.openide.util.WeakListeners; +import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.CasePreferences; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.mainui.datamodel.HostPersonDAO; import org.sleuthkit.autopsy.mainui.datamodel.HostSearchParams; import org.sleuthkit.autopsy.mainui.datamodel.MainDAO; import org.sleuthkit.autopsy.mainui.datamodel.OsAccountsSearchParams; @@ -46,7 +48,6 @@ import org.sleuthkit.autopsy.mainui.datamodel.events.DAOEvent; import org.sleuthkit.autopsy.mainui.datamodel.events.HostPersonEvent; import org.sleuthkit.autopsy.mainui.datamodel.events.TreeEvent; import org.sleuthkit.autopsy.mainui.nodes.TreeNode.StaticTreeNode; -import static org.sleuthkit.autopsy.mainui.nodes.TreeNode.getDefaultLookup; import org.sleuthkit.datamodel.Person; /** @@ -164,11 +165,11 @@ public class RootFactory { public static class PersonNode extends TreeNode { /** - * Returns the name identifier of this node. + * Returns the name prefix of this node type. * - * @return The name identifier. + * @return The name prefix. */ - public static final String getNameIdentifier() { + public static final String getNamePrefix() { return PersonSearchParams.getTypeId(); } @@ -183,11 +184,16 @@ public class RootFactory { } public PersonNode(TreeResultsDTO.TreeItemDTO itemData) { - super(PersonSearchParams.getTypeId(), + super(PersonSearchParams.getTypeId() + getLongString( + itemData.getSearchParams().getPerson() == null + ? 0 + : itemData.getSearchParams().getPerson().getPersonId()), "org/sleuthkit/autopsy/images/person.png", itemData, Children.create(new HostFactory(itemData.getSearchParams().getPerson()), true), - getDefaultLookup(itemData)); + itemData.getSearchParams().getPerson() != null + ? Lookups.fixed(itemData.getSearchParams(), itemData.getSearchParams().getPerson()) + : Lookups.fixed(itemData.getSearchParams(), HostPersonDAO.getUnknownPersonsName())); } } @@ -242,20 +248,40 @@ public class RootFactory { } public static class HostNode extends TreeNode { - + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return HostSearchParams.getTypeId(); + } + public HostNode(TreeResultsDTO.TreeItemDTO itemData) { - super(HostSearchParams.getTypeId(), + super(HostSearchParams.getTypeId() + "_" + getLongString(itemData.getSearchParams().getHost().getHostId()), "org/sleuthkit/autopsy/images/host.png", itemData, Children.create(new FileSystemFactory(itemData.getSearchParams().getHost()), true), - getDefaultLookup(itemData)); + Lookups.fixed(itemData.getSearchParams(), itemData.getSearchParams().getHost())); } } public static class DataSourceGroupedNode extends StaticTreeNode { + private static final String NAME_PREFIX = "DATA_SOURCE_GROUPED"; + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + public DataSourceGroupedNode(long dataSourceObjId, String dsName, boolean isImage) { - super("DATA_SOURCE_GROUPED_" + dataSourceObjId, + super(NAME_PREFIX + "_" + dataSourceObjId, dsName, isImage ? "org/sleuthkit/autopsy/images/image.png" : "org/sleuthkit/autopsy/images/fileset-icon-16.png", new DataSourceGroupedFactory(dataSourceObjId)); @@ -281,19 +307,19 @@ public class RootFactory { @Messages({"RootFactory_DataSourceFilesNode_displayName=Data Source Files"}) public static class DataSourceFilesNode extends StaticTreeNode { - private static final String NAME_ID = "DATA_SOURCE_FILES"; + private static final String NAME_PREFIX = "DATA_SOURCE_FILES"; /** - * Returns the name identifier of this node. + * Returns the name prefix of this node. * - * @return The name identifier. + * @return The name prefix. */ - public static final String getNameIdentifier() { - return NAME_ID; + public static final String getNamePrefix() { + return NAME_PREFIX; } public DataSourceFilesNode(long dataSourceObjId) { - super(NAME_ID, + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_DataSourceFilesNode_displayName(), "org/sleuthkit/autopsy/images/image.png", new FileSystemFactory(dataSourceObjId)); @@ -303,8 +329,19 @@ public class RootFactory { @Messages({"RootFactory_ViewsRootNode_displayName=Views"}) public static class ViewsRootNode extends StaticTreeNode { + private static final String NAME_PREFIX = "VIEWS"; + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + public ViewsRootNode(Long dataSourceObjId) { - super("VIEWS_" + getLongString(dataSourceObjId), + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_ViewsRootNode_displayName(), "org/sleuthkit/autopsy/images/views.png", new ViewsTypeFactory.ViewsChildren(dataSourceObjId)); @@ -314,8 +351,19 @@ public class RootFactory { @Messages({"RootFactory_DataArtifactsRootNode_displayName=Data Artifacts"}) public static class DataArtifactsRootNode extends StaticTreeNode { + private static final String NAME_PREFIX = "DATA_ARTIFACT"; + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + public DataArtifactsRootNode(Long dataSourceObjId) { - super("DATA_ARTIFACT_" + getLongString(dataSourceObjId), + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_DataArtifactsRootNode_displayName(), "org/sleuthkit/autopsy/images/extracted_content.png", new DataArtifactTypeFactory(dataSourceObjId)); @@ -325,8 +373,19 @@ public class RootFactory { @Messages({"RootFactory_AnalysisResultsRootNode_displayName=Analysis Results"}) public static class AnalysisResultsRootNode extends StaticTreeNode { + private static final String NAME_PREFIX = "DATA_SOURCE_BY_TYPE"; + + /** + * Returns the name identifier of this node. + * + * @return The name identifier. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + public AnalysisResultsRootNode(Long dataSourceObjId) { - super("DATA_SOURCE_BY_TYPE_" + getLongString(dataSourceObjId), + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_AnalysisResultsRootNode_displayName(), "org/sleuthkit/autopsy/images/analysis_result.png", new AnalysisResultTypeFactory(dataSourceObjId)); @@ -336,10 +395,21 @@ public class RootFactory { @Messages({"RootFactory_OsAccountsRootNode_displayName=OS Accounts"}) public static class OsAccountsRootNode extends StaticTreeNode { + private static final String NAME_PREFIX = "OS_ACCOUNTS"; + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + private final Long dataSourceObjId; public OsAccountsRootNode(Long dataSourceObjId) { - super("DATA_SOURCE_BY_TYPE_" + getLongString(dataSourceObjId), + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_OsAccountsRootNode_displayName(), "org/sleuthkit/autopsy/images/os-account.png"); @@ -356,8 +426,19 @@ public class RootFactory { @Messages({"RootFactory_TagsRootNode_displayName=Tags"}) public static class TagsRootNode extends StaticTreeNode { + private static final String NAME_PREFIX = "DATA_SOURCE_BY_TYPE"; + + /** + * Returns the name prefix of this node. + * + * @return The name prefix. + */ + public static final String getNamePrefix() { + return NAME_PREFIX; + } + public TagsRootNode(Long dataSourceObjId) { - super("DATA_SOURCE_BY_TYPE_" + getLongString(dataSourceObjId), + super(NAME_PREFIX + "_" + getLongString(dataSourceObjId), Bundle.RootFactory_TagsRootNode_displayName(), "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png", new TagNameFactory(dataSourceObjId)); @@ -367,8 +448,19 @@ public class RootFactory { @Messages({"RootFactory_ReportsRootNode_displayName=Reports"}) public static class ReportsRootNode extends StaticTreeNode { + private static final String NAME_ID = "REPORTS"; + + /** + * Returns the name identifier of this node. + * + * @return The name identifier. + */ + public static final String getNameIdentifier() { + return NAME_ID; + } + public ReportsRootNode() { - super("REPORTS", + super(NAME_ID, Bundle.RootFactory_ReportsRootNode_displayName(), "org/sleuthkit/autopsy/images/report_16.png"); }