diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index eb736069a7..03066faa55 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -51,8 +51,10 @@ abstract class AbstractContentChildren extends Keys { * Uses lazy Content.Keys */ AbstractContentChildren() { - /*This was turned off because we were getting out of memory errors when the - filter nodes were hiding nodes. Turning this off seemed to help */ + /* + * This was turned off because we were getting out of memory errors when + * the filter nodes were hiding nodes. Turning this off seemed to help + */ super(false); //don't use lazy behavior } @@ -138,7 +140,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(FileTypesByExtension sf) { - return new org.sleuthkit.autopsy.datamodel.FileTypesByExtension.FileTypesByExtNode(sf.getSleuthkitCase(), null); + return sf.new FileTypesByExtNode(sf.getSleuthkitCase(), null); } @Override @@ -198,7 +200,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(FileTypes ft) { - return new FileTypesNode(ft.getSleuthkitCase()); + return ft.new FileTypesNode(); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java index f3941d563e..0878a5a451 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -19,10 +19,18 @@ package org.sleuthkit.autopsy.datamodel; import java.util.Arrays; +import java.util.Observable; +import java.util.Observer; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.SwingWorker; import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; import org.openide.nodes.Sheet; +import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.DerivedFile; @@ -32,13 +40,17 @@ import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.SlackFile; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * File Types node support */ public final class FileTypes implements AutopsyVisitableItem { + private final static Logger logger = Logger.getLogger(FileTypes.class.getName()); + private final SleuthkitCase skCase; + private boolean showCounts = true; FileTypes(SleuthkitCase skCase) { this.skCase = skCase; @@ -53,22 +65,41 @@ public final class FileTypes implements AutopsyVisitableItem { return skCase; } + /** + * Should the nodes show counts? + * + * + * @return True, unless the DB has more than 200k rows. + */ + boolean shouldShowCounts() { + if (showCounts) { + try { + if (skCase.countFilesWhere("1=1") > 200000) { //NON-NLS + showCounts = false; + } + } catch (TskCoreException tskCoreException) { + showCounts = false; + logger.log(Level.SEVERE, "Error counting files.", tskCoreException); //NON-NLS + } + } + return showCounts; + } + @NbBundle.Messages("FileTypes.name.text=File Types") + static private final String NAME = Bundle.FileTypes_name_text(); + /** * Node which will contain By Mime Type and By Extension nodes. */ - public static final class FileTypesNode extends DisplayableItemNode { + public final class FileTypesNode extends DisplayableItemNode { - @NbBundle.Messages("FileTypes.name.text=File Types") - private static final String NAME = Bundle.FileTypes_name_text(); - - FileTypesNode(SleuthkitCase sleuthkitCase) { + FileTypesNode() { super(new RootContentChildren(Arrays.asList( - new FileTypesByExtension(sleuthkitCase), - new FileTypesByMimeType(sleuthkitCase) - )), Lookups.singleton(NAME)); + new FileTypesByExtension(FileTypes.this), + new FileTypesByMimeType(FileTypes.this))), + Lookups.singleton(NAME)); setName(NAME); setDisplayName(NAME); - this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); //NON-NLS } @Override @@ -149,4 +180,67 @@ public final class FileTypes implements AutopsyVisitableItem { throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(), "FileTypeChildren.exception.notSupported.msg", di.toString())); } } + + static abstract class BGCountUpdatingNode extends DisplayableItemNode implements Observer { + + private long childCount = -1; + private FileTypes typesRoot; + + BGCountUpdatingNode(FileTypes typesRoot, Children children) { + this(typesRoot, children, null); + } + + BGCountUpdatingNode(FileTypes typesRoot, Children children, Lookup lookup) { + super(children, lookup); + this.typesRoot = typesRoot; + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + abstract String getDisplayNameBase(); + + /** + * Calculate the number of children of this node, possibly by querying + * the DB. + * + * @return @throws TskCoreException if there was an error querying the + * DB to calculate the number of children. + */ + abstract long calculateChildCount() throws TskCoreException; + + /** + * Updates the display name of the mediaSubTypeNode to include the count + * of files which it represents. + */ + @NbBundle.Messages("FileTypes.bgCounting.placeholder=(counting...)") + void updateDisplayName() { + if (typesRoot.shouldShowCounts()) { + //only show "(counting...)" the first time, otherwise it is distracting. + setDisplayName(getDisplayNameBase() + ((childCount < 0) ? Bundle.FileTypes_bgCounting_placeholder() + : ("(" + childCount + ")"))); //NON-NLS + new SwingWorker() { + @Override + protected Long doInBackground() throws Exception { + return calculateChildCount(); + } + + @Override + protected void done() { + try { + childCount = get(); + setDisplayName(getDisplayNameBase() + " (" + childCount + ")"); //NON-NLS + } catch (InterruptedException | ExecutionException ex) { + setDisplayName(getDisplayNameBase()); + logger.log(Level.WARNING, "Failed to get count of files for " + getDisplayNameBase(), ex); //NON-NLS + } + } + }.execute(); + } else { + setDisplayName(getDisplayNameBase() + ((childCount < 0) ? "" : ("(" + childCount + "+)"))); //NON-NLS + } + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java index 819db21e5e..7db5f2d652 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtension.java @@ -35,7 +35,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; @@ -46,11 +45,13 @@ import org.sleuthkit.datamodel.TskData; */ public final class FileTypesByExtension implements AutopsyVisitableItem { - private static final Logger logger = Logger.getLogger(FileTypesByExtension.class.getName()); + private final static Logger logger = Logger.getLogger(FileTypesByExtension.class.getName()); private final SleuthkitCase skCase; + private final FileTypes typesRoot; - public FileTypesByExtension(SleuthkitCase skCase) { - this.skCase = skCase; + public FileTypesByExtension(FileTypes typesRoot) { + this.skCase = typesRoot.getSleuthkitCase(); + this.typesRoot = typesRoot; } public SleuthkitCase getSleuthkitCase() { @@ -66,16 +67,18 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. */ - static private class FileTypesByExtObservable extends Observable { + private class FileTypesByExtObservable extends Observable { - private boolean showCounts = true; private final PropertyChangeListener pcl; - private FileTypesByExtObservable(SleuthkitCase skCase) { + private FileTypesByExtObservable() { super(); this.pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString()) || eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { /** * Checking for a current case is a stop gap measure until a * different way of handling the closing of cases is worked @@ -84,7 +87,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { */ try { Case.getCurrentCase(); - shouldShowCounts(skCase); + typesRoot.shouldShowCounts(); update(); } catch (IllegalStateException notUsed) { /** @@ -105,26 +108,6 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { } - /** - * Should the nodes show counts? - * - * - * @return True, unless the DB has more than 200k rows. - */ - private boolean shouldShowCounts(SleuthkitCase skCase) { - if (showCounts) { - try { - if (skCase.countFilesWhere("1=1") > 200000) { - showCounts = false; - } - } catch (TskCoreException tskCoreException) { - showCounts = false; - logger.log(Level.SEVERE, "Error counting files.", tskCoreException); - } - } - return showCounts; - } - private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); @@ -137,13 +120,13 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { notifyObservers(); } } + private static final String FNAME = NbBundle.getMessage(FileTypesByExtNode.class, "FileTypesByExtNode.fname.text"); /** * Node for root of file types view. Children are nodes for specific types. */ - static class FileTypesByExtNode extends DisplayableItemNode { + class FileTypesByExtNode extends DisplayableItemNode { - private static final String FNAME = NbBundle.getMessage(FileTypesByExtNode.class, "FileTypesByExtNode.fname.text"); private final FileTypesByExtension.RootFilter filter; /** @@ -153,9 +136,7 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * something to provide a sub-node. */ FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter) { - super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, null), true), Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); - this.filter = filter; - init(); + this(skCase, filter, null); } /** @@ -166,12 +147,11 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * provides updates on events */ private FileTypesByExtNode(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { - super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); - this.filter = filter; - init(); - } - private void init() { + super(Children.create(new FileTypesByExtNodeChildren(skCase, filter, o), true), + Lookups.singleton(filter == null ? FNAME : filter.getDisplayName())); + this.filter = filter; + // root node of tree if (filter == null) { super.setName(FNAME); @@ -230,68 +210,66 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return getClass().getName(); } - private static class FileTypesByExtNodeChildren extends ChildFactory { + } - private final SleuthkitCase skCase; - private final FileTypesByExtension.RootFilter filter; - private final FileTypesByExtObservable notifier; + private class FileTypesByExtNodeChildren extends ChildFactory { - /** - * - * @param skCase - * @param filter Is null for root node - * @param o Observable that provides updates based on events - * being fired (or null if one needs to be created) - */ - private FileTypesByExtNodeChildren(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { - super(); - this.skCase = skCase; - this.filter = filter; - if (o == null) { - this.notifier = new FileTypesByExtObservable(skCase); - } else { - this.notifier = o; - } - } + private final SleuthkitCase skCase; + private final FileTypesByExtension.RootFilter filter; + private final FileTypesByExtObservable notifier; - @Override - protected boolean createKeys(List list) { - // root node - if (filter == null) { - list.addAll(Arrays.asList(FileTypesByExtension.RootFilter.values())); - } // document and executable has another level of nodes - else if (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER)) { - list.addAll(Arrays.asList(FileTypesByExtension.DocumentFilter.values())); - } else if (filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER)) { - list.addAll(Arrays.asList(FileTypesByExtension.ExecutableFilter.values())); - } - return true; - } - - @Override - protected Node createNodeForKey(FileTypesByExtension.SearchFilterInterface key) { - // make new nodes for the sub-nodes - if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER.getName())) { - return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER, notifier); - } else if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER.getName())) { - return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER, notifier); - } else { - return new FileExtensionNode(key, skCase, notifier); - } + /** + * + * @param skCase + * @param filter Is null for root node + * @param o Observable that provides updates based on events being + * fired (or null if one needs to be created) + */ + private FileTypesByExtNodeChildren(SleuthkitCase skCase, FileTypesByExtension.RootFilter filter, FileTypesByExtObservable o) { + super(); + this.skCase = skCase; + this.filter = filter; + if (o == null) { + this.notifier = new FileTypesByExtObservable(); + } else { + this.notifier = o; } } + @Override + protected boolean createKeys(List list) { + // root node + if (filter == null) { + list.addAll(Arrays.asList(FileTypesByExtension.RootFilter.values())); + } // document and executable has another level of nodes + else if (filter.equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER)) { + list.addAll(Arrays.asList(FileTypesByExtension.DocumentFilter.values())); + } else if (filter.equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER)) { + list.addAll(Arrays.asList(FileTypesByExtension.ExecutableFilter.values())); + } + return true; + } + + @Override + protected Node createNodeForKey(FileTypesByExtension.SearchFilterInterface key) { + // make new nodes for the sub-nodes + if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER.getName())) { + return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_DOCUMENT_FILTER, notifier); + } else if (key.getName().equals(FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER.getName())) { + return new FileTypesByExtNode(skCase, FileTypesByExtension.RootFilter.TSK_EXECUTABLE_FILTER, notifier); + } else { + return new FileExtensionNode(key, skCase, notifier); + } + } } /** * Node for a specific file type / extension. Children of it will be the * files of that type. */ - static class FileExtensionNode extends DisplayableItemNode { + class FileExtensionNode extends FileTypes.BGCountUpdatingNode { - FileTypesByExtension.SearchFilterInterface filter; - SleuthkitCase skCase; - private final FileTypesByExtObservable notifier; + private final FileTypesByExtension.SearchFilterInterface filter; /** * @@ -301,34 +279,14 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { * should refresh */ FileExtensionNode(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, FileTypesByExtObservable o) { - super(Children.create(new FileExtensionNodeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); + super(typesRoot, Children.create(new FileExtensionNodeChildren(filter, skCase, o), true), + Lookups.singleton(filter.getDisplayName())); this.filter = filter; - this.skCase = skCase; - this.notifier = o; - init(); - o.addObserver(new ByExtNodeObserver()); - } - - private void init() { super.setName(filter.getDisplayName()); updateDisplayName(); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS - } - // update the display name when new events are fired - private class ByExtNodeObserver implements Observer { - - @Override - public void update(Observable o, Object arg) { - updateDisplayName(); - } - } - - private void updateDisplayName() { - final String count = notifier.shouldShowCounts(skCase) - ? " (" + Long.toString(FileExtensionNodeChildren.calculateItems(skCase, filter)) + ")" - : ""; - super.setDisplayName(filter.getDisplayName() + count); + o.addObserver(this); } @Override @@ -344,13 +302,15 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { ss = Sheet.createPropertiesSet(); s.put(ss); } - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.desc"), filter.getDisplayName())); - String extensions = ""; - for (String ext : filter.getFilter()) { - extensions += "'" + ext + "', "; - } - extensions = extensions.substring(0, extensions.lastIndexOf(',')); - ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), extensions)); + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.name"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.displayName"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.filterType.desc"), + filter.getDisplayName())); + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.name"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.displayName"), + NbBundle.getMessage(this.getClass(), "FileTypesByExtNode.createSheet.fileExt.desc"), + String.join(", ", filter.getFilter()))); return s; } @@ -369,98 +329,86 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { return DisplayableItemNode.FILE_PARENT_NODE_KEY; } + @Override + String getDisplayNameBase() { + return filter.getDisplayName(); + } + + @Override + long calculateChildCount() throws TskCoreException { + return skCase.countFilesWhere(createQuery(filter)); + } + } + + private static String createQuery(FileTypesByExtension.SearchFilterInterface filter) { + StringBuilder query = new StringBuilder(); + query.append("(dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()).append(")"); //NON-NLS + if (UserPreferences.hideKnownFilesInViewsTree()) { + query.append(" AND (known IS NULL OR known != ").append(TskData.FileKnown.KNOWN.getFileKnownValue()).append(")"); //NON-NLS + } + query.append(" AND (NULL"); //NON-NLS + for (String s : filter.getFilter()) { + query.append(" OR LOWER(name) LIKE LOWER('%").append(s).append("')"); //NON-NLS + } + query.append(')'); + return query.toString(); + } + + /** + * Child node factory for a specific file type - does the database query. + */ + private static class FileExtensionNodeChildren extends ChildFactory.Detachable implements Observer { + + private final SleuthkitCase skCase; + private final FileTypesByExtension.SearchFilterInterface filter; + private final Observable notifier; + /** - * Child node factory for a specific file type - does the database - * query. + * + * @param filter Extensions to display + * @param skCase + * @param o Observable that will notify when there could be new + * data to display */ - private static class FileExtensionNodeChildren extends ChildFactory.Detachable { + private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { + super(); + this.filter = filter; + this.skCase = skCase; + notifier = o; + } - private final SleuthkitCase skCase; - private final FileTypesByExtension.SearchFilterInterface filter; - private static final Logger LOGGER = Logger.getLogger(FileExtensionNodeChildren.class.getName()); - private final Observable notifier; - - /** - * - * @param filter Extensions to display - * @param skCase - * @param o Observable that will notify when there could be new - * data to display - */ - private FileExtensionNodeChildren(FileTypesByExtension.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { - super(); - this.filter = filter; - this.skCase = skCase; - notifier = o; + @Override + protected void addNotify() { + if (notifier != null) { + notifier.addObserver(this); } + } - @Override - protected void addNotify() { - if (notifier != null) { - notifier.addObserver(observer); - } + @Override + protected void removeNotify() { + if (notifier != null) { + notifier.deleteObserver(this); } + } - @Override - protected void removeNotify() { - if (notifier != null) { - notifier.deleteObserver(observer); - } + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + @Override + protected boolean createKeys(List list) { + try { + list.addAll(skCase.findAllFilesWhere(createQuery(filter))); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS } - private final Observer observer = new FileTypeChildFactoryObserver(); + return true; + } - // Cause refresh of children if there are changes - private class FileTypeChildFactoryObserver implements Observer { - - @Override - public void update(Observable o, Object arg) { - refresh(true); - } - } - - /** - * Get children count without actually loading all nodes - * - * @return - */ - private static long calculateItems(SleuthkitCase sleuthkitCase, FileTypesByExtension.SearchFilterInterface filter) { - try { - return sleuthkitCase.countFilesWhere(createQuery(filter)); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting file search view count", ex); //NON-NLS - return 0; - } - } - - @Override - protected boolean createKeys(List list) { - try { - List files = skCase.findAllFilesWhere(createQuery(filter)); - list.addAll(files); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS - } - return true; - } - - private static String createQuery(FileTypesByExtension.SearchFilterInterface filter) { - StringBuilder query = new StringBuilder(); - query.append("(dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()).append(")"); //NON-NLS - if (UserPreferences.hideKnownFilesInViewsTree()) { - query.append(" AND (known IS NULL OR known != ").append(TskData.FileKnown.KNOWN.getFileKnownValue()).append(")"); //NON-NLS - } - query.append(" AND (NULL"); //NON-NLS - for (String s : filter.getFilter()) { - query.append(" OR LOWER(name) LIKE LOWER('%").append(s).append("')"); //NON-NLS - } - query.append(')'); - return query.toString(); - } - - @Override - protected Node createNodeForKey(Content key) { - return key.accept(new FileTypes.FileNodeCreationVisitor()); - } + @Override + protected Node createNodeForKey(Content key) { + return key.accept(new FileTypes.FileNodeCreationVisitor()); } } @@ -637,5 +585,6 @@ public final class FileTypesByExtension implements AutopsyVisitableItem { public String getDisplayName(); public List getFilter(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index d7c777fa98..59a9f984ab 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -26,10 +26,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.logging.Level; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -52,20 +52,20 @@ import org.sleuthkit.datamodel.TskData; * File Types view, shows all files with a mime type. Will initially be empty * until file type identification has been performed. Contains a Property Change * Listener which is checking for changes in IngestJobEvent Completed or - * Cancelled and IngestModuleEvent Content Changed. + * Canceled and IngestModuleEvent Content Changed. */ public final class FileTypesByMimeType extends Observable implements AutopsyVisitableItem { + private final static Logger logger = Logger.getLogger(FileTypesByMimeType.class.getName()); private final SleuthkitCase skCase; /** * The nodes of this tree will be determined dynamically by the mimetypes * which exist in the database. This hashmap will store them with the media - * type as the key and a list of media subtypes as the value. + * type as the key and a Map, from media subtype to count, as the value. */ - private final HashMap> existingMimeTypes = new HashMap<>(); + private final HashMap> existingMimeTypeCounts = new HashMap<>(); private static final Logger LOGGER = Logger.getLogger(FileTypesByMimeType.class.getName()); - - private boolean showCounts = true; + private final FileTypes typesRoot; private void removeListeners() { deleteObservers(); @@ -78,54 +78,40 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi */ private final PropertyChangeListener pcl; - /** - * Retrieve the media types by retrieving the keyset from the hashmap. - * - * @return mediaTypes - a list of strings representing all distinct media - * types of files for this case - */ - private List getMediaTypeList() { - synchronized (existingMimeTypes) { - List mediaTypes = new ArrayList<>(existingMimeTypes.keySet()); - Collections.sort(mediaTypes); - return mediaTypes; - } - } - /** * Performs the query on the database to get all distinct MIME types of * files in it, and populate the hashmap with those results. */ private void populateHashMap() { - StringBuilder allDistinctMimeTypesQuery = new StringBuilder(); - allDistinctMimeTypesQuery.append("SELECT DISTINCT mime_type from tsk_files where mime_type IS NOT null"); //NON-NLS - allDistinctMimeTypesQuery.append(" AND dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()); //NON-NLS - allDistinctMimeTypesQuery.append(" AND (type IN (").append(TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal()).append(","); //NON-NLS - allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal()).append(","); - allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal()).append(","); - if (!UserPreferences.hideSlackFilesInViewsTree()) { - allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal()).append(","); - } - allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()).append("))"); - synchronized (existingMimeTypes) { - existingMimeTypes.clear(); + String query = "SELECT mime_type,count(*) as count from tsk_files " + + " where mime_type IS NOT null " + + " AND dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + + " AND (type IN (" + + TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal() + "," + + TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal() + + ((UserPreferences.hideSlackFilesInViewsTree()) ? "" + : ("," + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.ordinal())) + + "))" + " GROUP BY mime_type"; + synchronized (existingMimeTypeCounts) { + existingMimeTypeCounts.clear(); if (skCase == null) { return; } - try (SleuthkitCase.CaseDbQuery dbQuery = skCase.executeQuery(allDistinctMimeTypesQuery.toString())) { + try (SleuthkitCase.CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { final String mime_type = resultSet.getString("mime_type"); //NON-NLS if (!mime_type.isEmpty()) { - String mimeType[] = mime_type.split("/"); //if the mime_type contained multiple slashes then everything after the first slash will become the subtype - final String mimeMediaSubType = StringUtils.join(ArrayUtils.subarray(mimeType, 1, mimeType.length), "/"); - if (mimeType.length > 1 && !mimeType[0].isEmpty() && !mimeMediaSubType.isEmpty()) { - if (!existingMimeTypes.containsKey(mimeType[0])) { - existingMimeTypes.put(mimeType[0], new ArrayList<>()); - } - existingMimeTypes.get(mimeType[0]).add(mimeMediaSubType); + final String mediaType = StringUtils.substringBefore(mime_type, "/"); + final String subType = StringUtils.removeStart(mime_type, mediaType + "/"); + if (!mediaType.isEmpty() && !subType.isEmpty()) { + final long count = resultSet.getLong("count"); + existingMimeTypeCounts.computeIfAbsent(mediaType, t -> new HashMap<>()) + .put(subType, count); } } } @@ -135,11 +121,12 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } setChanged(); - notifyObservers(); } - FileTypesByMimeType(SleuthkitCase skCase) { + FileTypesByMimeType(FileTypes typesRoot) { + this.skCase = typesRoot.getSleuthkitCase(); + this.typesRoot = typesRoot; this.pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) @@ -153,8 +140,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi */ try { Case.getCurrentCase(); - shouldShowCounts(skCase); - + typesRoot.shouldShowCounts(); populateHashMap(); } catch (IllegalStateException notUsed) { /** @@ -169,30 +155,9 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi }; IngestManager.getInstance().addIngestJobEventListener(pcl); Case.addPropertyChangeListener(pcl); - this.skCase = skCase; populateHashMap(); } - /** - * Should the nodes show counts? - * - * - * @return True, unless the DB has more than 200k rows. - */ - private boolean shouldShowCounts(final SleuthkitCase skCase) { - if (showCounts) { - try { - if (skCase.countFilesWhere("1=1") > 200000) { - showCounts = false; - } - } catch (TskCoreException tskCoreException) { - showCounts = false; - LOGGER.log(Level.SEVERE, "Error counting files.", tskCoreException); - } - } - return showCounts; - } - @Override public T accept(AutopsyItemVisitor v) { return v.visit(this); @@ -251,9 +216,10 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } boolean isEmpty() { - return existingMimeTypes.isEmpty(); + synchronized (existingMimeTypeCounts) { + return existingMimeTypeCounts.isEmpty(); + } } - } /** @@ -269,9 +235,13 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi @Override protected boolean createKeys(List mediaTypeNodes) { - if (!existingMimeTypes.isEmpty()) { - mediaTypeNodes.addAll(getMediaTypeList()); + final List keylist; + synchronized (existingMimeTypeCounts) { + keylist = new ArrayList<>(existingMimeTypeCounts.keySet()); } + Collections.sort(keylist); + mediaTypeNodes.addAll(keylist); + return true; } @@ -284,7 +254,6 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi public void update(Observable o, Object arg) { refresh(true); } - } /** @@ -349,7 +318,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi @Override protected boolean createKeys(List mediaTypeNodes) { - mediaTypeNodes.addAll(existingMimeTypes.get(mediaType)); + mediaTypeNodes.addAll(existingMimeTypeCounts.get(mediaType).keySet()); return true; } @@ -370,33 +339,22 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * Node which represents the media sub type in the By MIME type tree, the * media subtype is the portion of the MIME type following the /. */ - class MediaSubTypeNode extends DisplayableItemNode implements Observer { + class MediaSubTypeNode extends FileTypes.BGCountUpdatingNode { @NbBundle.Messages({"FileTypesByMimeTypeNode.createSheet.mediaSubtype.name=Subtype", "FileTypesByMimeTypeNode.createSheet.mediaSubtype.displayName=Subtype", "FileTypesByMimeTypeNode.createSheet.mediaSubtype.desc=no description"}) + private final String mimeType; + private final String subType; + private MediaSubTypeNode(String mimeType) { - super(Children.create(new MediaSubTypeNodeChildren(mimeType), true),Lookups.singleton(mimeType)); - addObserver(this); - init(mimeType); - } - - private void init(String mimeType) { + super(typesRoot, Children.create(new MediaSubTypeNodeChildren(mimeType), true), Lookups.singleton(mimeType)); + this.mimeType = mimeType; + this.subType = StringUtils.substringAfter(mimeType, "/"); super.setName(mimeType); - updateDisplayName(mimeType); + super.setDisplayName(subType); + updateDisplayName(); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS - } - - /** - * Updates the display name of the mediaSubTypeNode to include the count - * of files which it represents. - * - * @param mimeType - the complete MimeType, needed for accurate query - * results - */ - private void updateDisplayName(String mimeType) { - final long count = calculateItems(skCase, mimeType); - //only the part of the mimeType after the media type - super.setDisplayName(StringUtils.substringAfter(mimeType, "/") + " (" + count + ")"); + addObserver(this); } /** @@ -411,7 +369,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } @Override - public T accept(DisplayableItemNodeVisitor v) { + public T accept(DisplayableItemNodeVisitor< T> v) { return v.visit(this); } @Override @@ -433,22 +391,17 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi @Override public void update(Observable o, Object arg) { - updateDisplayName(getName()); + updateDisplayName(); } - } - /** - * Get children count without actually loading all nodes - * - * @return count(*) - the number of items that will be shown in this items - * Directory Listing - */ - static private long calculateItems(SleuthkitCase sleuthkitCase, String mime_type) { - try { - return sleuthkitCase.countFilesWhere(createQuery(mime_type)); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting file search view count", ex); //NON-NLS - return 0; + @Override + String getDisplayNameBase() { + return subType; + } + + @Override + long calculateChildCount() { + return existingMimeTypeCounts.get(StringUtils.substringBefore(mimeType, "/")).get(subType); } } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java b/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java index 8fbeb1d19c..6ac324e31f 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/FactoryClassNameNormalizer.java @@ -34,7 +34,7 @@ class FactoryClassNameNormalizer { // of their file name. This block of code gets rid of that variable // instance number and helps maitains constant module name over // multiple runs. - String moduleClassName = canonicalClassName.replaceAll("\\d*$", ""); //NON-NLS NON-NLS + String moduleClassName = canonicalClassName.replaceAll("\\$\\d*$", ""); //NON-NLS NON-NLS return moduleClassName; } return canonicalClassName;