/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.directorytree;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.openide.nodes.Children;
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode;
import org.sleuthkit.autopsy.datamodel.LayoutFileNode;
import org.sleuthkit.autopsy.datamodel.LocalFileNode;
import org.sleuthkit.autopsy.datamodel.SlackFileNode;
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
import org.sleuthkit.autopsy.datamodel.VolumeNode;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
import org.sleuthkit.datamodel.VirtualDirectory;
import org.sleuthkit.datamodel.Volume;
/**
* This class wraps around nodes that are displayed in the directory tree and
* hides files, '..', and other children that should not be displayed. facility
* to customize nodes view in dir tree: hide them or set no children
*/
class DirectoryTreeFilterChildren extends FilterNode.Children {
private final ShowItemVisitor showItemV = new ShowItemVisitor();
private final IsLeafItemVisitor isLeafItemV = new IsLeafItemVisitor();
private final static Logger logger = Logger.getLogger(DirectoryTreeFilterChildren.class.getName());
/**
* the constructor
*/
public DirectoryTreeFilterChildren(Node arg) {
super(arg);
}
@Override
protected Node copyNode(Node arg0) {
return new DirectoryTreeFilterNode(arg0, true);
}
protected Node copyNode(Node arg0, boolean createChildren) {
return new DirectoryTreeFilterNode(arg0, createChildren);
}
/*
* This method takes in a node as an argument and will create a new one if
* it should be displayed in the tree. If it is to be displayed, it also
* figures out if it is a leaf or not (i.e. should it have a + sign in the
* tree).
*
* It does NOT create children nodes
*/
@Override
protected Node[] createNodes(Node origNode) {
if (origNode == null || !(origNode instanceof DisplayableItemNode)) {
return new Node[]{};
}
// Shoudl this node be displayed in the tree or not
final DisplayableItemNode diNode = (DisplayableItemNode) origNode;
if (diNode.accept(showItemV) == false) {
//do not show
return new Node[]{};
}
// If it is going to be displayed, then determine if it should
// have a '+' next to it based on if it has children of itself.
// We will filter out the "." and ".." directories
final boolean isLeaf = diNode.accept(isLeafItemV);
return new Node[]{this.copyNode(origNode, !isLeaf)};
}
/**
* Don't show expansion button on leaves leaf: all children are (file) or
* (directory named "." or "..")
*
* @param node
*
* @return whether node is a leaf
*/
private static boolean isLeafDirectory(DirectoryNode node) {
Directory dir = node.getLookup().lookup(Directory.class);
boolean ret = true;
try {
for (Content c : dir.getChildren()) {
if (c instanceof Directory && (!((Directory) c).getName().equals(".")
&& !((Directory) c).getName().equals(".."))) {
ret = false;
break;
} else if (c.hasChildren()) {
//fie has children, such as derived files
ret = false;
break;
}
}
} catch (TskException ex) {
Logger.getLogger(DirectoryTreeFilterChildren.class.getName())
.log(Level.WARNING, "Error getting directory children", ex); //NON-NLS
return false;
}
return ret;
}
private static boolean isLeafVolume(VolumeNode node) {
Volume vol = node.getLookup().lookup(Volume.class);
boolean ret = true;
try {
for (Content c : vol.getChildren()) {
if (!(c instanceof LayoutFile
|| c instanceof VirtualDirectory)) {
ret = false;
break;
}
}
} catch (TskException ex) {
Logger.getLogger(DirectoryTreeFilterChildren.class.getName())
.log(Level.WARNING, "Error getting volume children", ex); //NON-NLS
return false;
}
return ret;
}
/**
* Helper to ignore the '.' and '..' directories
*/
private static boolean isDotDirectory(DirectoryNode dir) {
String name = dir.getDisplayName();
return name.equals(DirectoryNode.DOTDIR) || name.equals(DirectoryNode.DOTDOTDIR);
}
/**
* Return the children based on the current node given. If the node doesn't
* have any directory or volume or image node inside it, it just returns
* leaf.
*
* @param arg the node
*
* @return children the children
*/
public static Children createInstance(Node arg, boolean createChildren) {
if (createChildren) {
return new DirectoryTreeFilterChildren(arg);
} else {
return Children.LEAF;
}
}
private static class IsLeafItemVisitor extends DisplayableItemNodeVisitor.Default<Boolean> {
@Override
protected Boolean defaultVisit(DisplayableItemNode c) {
return c.isLeafTypeNode();
}
@Override
public Boolean visit(DirectoryNode dn) {
return isLeafDirectory(dn);
}
private Boolean visitDeep(AbstractAbstractFileNode<? extends AbstractFile> node) {
//is a leaf if has no children, or children are files not dirs
boolean hasChildren = node.hasContentChildren();
if (!hasChildren) {
return true;
}
List<Content> derivedChildren = node.getContentChildren();
//child of a file, must be a (derived) file too
for (Content childContent : derivedChildren) {
if (((AbstractFile) childContent).isDir()) {
return false;
} else {
try {
if (childContent.hasChildren()) {
return false;
}
} catch (TskCoreException e) {
logger.log(Level.SEVERE, "Error checking if file node is leaf.", e); //NON-NLS
}
}
}
return true;
}
@Override
public Boolean visit(FileNode fn) {
return visitDeep(fn);
}
@Override
public Boolean visit(LocalFileNode lfn) {
return visitDeep(lfn);
}
@Override
public Boolean visit(LayoutFileNode fn) {
return visitDeep(fn);
}
@Override
public Boolean visit(SlackFileNode sfn) {
return visitDeep(sfn);
}
@Override
public Boolean visit(VolumeNode vn) {
return isLeafVolume(vn);
}
@Override
public Boolean visit(VirtualDirectoryNode vdn) {
return visitDeep(vdn);
//return ! vdn.hasContentChildren();
}
@Override
public Boolean visit(FileTypesNode ft) {
return defaultVisit(ft);
}
}
private static class ShowItemVisitor extends DisplayableItemNodeVisitor.Default<Boolean> {
@Override
protected Boolean defaultVisit(DisplayableItemNode c) {
return true;
}
@Override
public Boolean visit(DirectoryNode dn) {
if (isDotDirectory(dn)) {
return false;
}
return true;
}
@Override
public Boolean visit(FileNode fn) {
return fn.hasContentChildren();
}
@Override
public Boolean visit(LocalFileNode lfn) {
return lfn.hasContentChildren();
}
@Override
public Boolean visit(LayoutFileNode ln) {
return ln.hasContentChildren();
}
@Override
public Boolean visit(SlackFileNode sfn) {
return sfn.hasContentChildren();
}
@Override
public Boolean visit(VirtualDirectoryNode vdn) {
return true;
//return vdn.hasContentChildren();
}
@Override
public Boolean visit(FileTypesNode fileTypes) {
return defaultVisit(fileTypes);
}
}
}