diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 9efcce9f7d..b58e310fe0 100644 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -76,6 +76,7 @@ public final class UserPreferences { public static final String DISPLAY_TRANSLATED_NAMES = "DisplayTranslatedNames"; public static final String EXTERNAL_HEX_EDITOR_PATH = "ExternalHexEditorPath"; public static final String SOLR_MAX_JVM_SIZE = "SolrMaxJVMSize"; + public static final String RESULTS_TABLE_PAGE_SIZE = "ResultsTablePageSize"; // Prevent instantiation. private UserPreferences() { @@ -491,6 +492,24 @@ public final class UserPreferences { preferences.putInt(SOLR_MAX_JVM_SIZE, maxSize); } + /** + * Get the maximum number of results to display in a result table. + * + * @return Saved value or default (0) which indicates no max. + */ + public static int getResultsTablePageSize() { + return preferences.getInt(RESULTS_TABLE_PAGE_SIZE, 0); + } + + /** + * Set the maximum number of results to display in a result table. + * + * @param pageSize + */ + public static void setResultsTablePageSize(int pageSize) { + preferences.putInt(RESULTS_TABLE_PAGE_SIZE, pageSize); + } + /** * Set the HdX path. * diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java new file mode 100644 index 0000000000..555d59b33c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BaseChildFactory.java @@ -0,0 +1,234 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import com.google.common.collect.Lists; +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.openide.nodes.ChildFactory; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.datamodel.Content; + +/** + * Abstract child factory that provides paging and filtering functionality to + * subclasses. + * + * @param + */ +public abstract class BaseChildFactory extends ChildFactory.Detachable { + + private final Predicate filter; + private boolean isPageChangeEvent; + + private final PagingSupport pagingSupport; + + /** + * This static map is used to facilitate communication between the UI and + * the child factory. + */ + public static Map nodeNameToEventBusMap = new ConcurrentHashMap<>(); + + public BaseChildFactory(String nodeName) { + pagingSupport = new PagingSupport(nodeName); + pagingSupport.initialize(); + isPageChangeEvent = false; + filter = new KnownAndSlackFilter<>(); + } + + @Override + protected void addNotify() { + onAdd(); + } + + @Override + protected void removeNotify() { + onRemove(); + } + + /** + * Subclasses implement this to construct a collection of keys. + * + * @return + */ + protected abstract List makeKeys(); + + /** + * Subclasses implement this to initialize any required resources. + */ + protected abstract void onAdd(); + + /** + * Subclasses implement this to clean up any resources they acquired in + * onAdd() + */ + protected abstract void onRemove(); + + @Override + protected boolean createKeys(List toPopulate) { + // For page chage events we simply return the previously calculated + // keys, otherwise we make a new set of keys. + if (!isPageChangeEvent) { + List allKeys = makeKeys(); + + // Filter keys + allKeys.stream().filter(filter).collect(Collectors.toList()); + + pagingSupport.splitKeysIntoPages(allKeys); + } + + toPopulate.addAll(pagingSupport.getCurrentPage()); + + // Reset page change event flag + isPageChangeEvent = false; + + return true; + } + + /** + * Event used to let subscribers know that the user has + * navigated to a different page. + */ + public static class PageChangeEvent { + + private final int pageNumber; + + public PageChangeEvent(int newPageNumber) { + pageNumber = newPageNumber; + } + + public int getPageNumber() { + return pageNumber; + } + } + + /** + * Event used to let subscribers know that the number of + * pages has changed. + */ + public static class PageCountChangeEvent { + + private final int pageCount; + + public PageCountChangeEvent(int newPageCount) { + pageCount = newPageCount; + } + + public int getPageCount() { + return pageCount; + } + } + + /** + * Event used to let subscribers know that paging is no + * longer required. + */ + public static class PagingDestroyedEvent { + } + + /** + * Class that supplies paging related functionality to the base child + * factory class. + */ + class PagingSupport { + + private final String nodeName; + private final int pageSize; + private int currentPage; + private List> pages; + private EventBus bus; + + /** + * Construct PagingSupport instance for the given node name. + * + * @param nodeName Name of the node in the tree for which results are + * being displayed. The node name is used to allow + * communication between the UI and the ChildFactory via + * an EventBus. + */ + PagingSupport(String nodeName) { + pageSize = UserPreferences.getResultsTablePageSize(); + this.currentPage = 1; + pages = new ArrayList<>(); + this.nodeName = nodeName; + } + + void initialize() { + if (pageSize > 0) { + // Only configure an EventBus if paging functionality is enabled. + if (nodeNameToEventBusMap.containsKey(nodeName)) { + bus = nodeNameToEventBusMap.get(nodeName); + } else { + bus = new EventBus(nodeName); + nodeNameToEventBusMap.put(bus.identifier(), bus); + } + bus.register(this); + } + } + + /** + * Get the list of keys at the current page. + * + * @return List of keys. + */ + List getCurrentPage() { + if (!pages.isEmpty()) { + return pages.get(currentPage - 1); + } + + return Collections.emptyList(); + } + + /** + * Split the given collection of keys into pages based on page size. + * + * @param keys + */ + void splitKeysIntoPages(List keys) { + int oldPageCount = pages.size(); + + /** + * If pageSize is set split keys into pages, otherwise create a + * single page containing all keys. + */ + pages = Lists.partition(keys, pageSize > 0 ? pageSize : keys.size()); + if (pages.size() != oldPageCount) { + // Number of pages has changed so we need to send out a notification. + bus.post(new PageCountChangeEvent(pages.size())); + } + } + + @Subscribe + private void subscribeToPageChange(PageChangeEvent event) { + // Receives page change events from UI components and + // triggers a refresh in the child factory. + if (event != null) { + currentPage = event.getPageNumber(); + isPageChangeEvent = true; + refresh(true); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KnownAndSlackFilter.java b/Core/src/org/sleuthkit/autopsy/datamodel/KnownAndSlackFilter.java new file mode 100644 index 0000000000..5c97a5a044 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KnownAndSlackFilter.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.function.Predicate; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Predicate that can be used to filter known and/or slack files from + * Content collections based on user preferences. + */ +class KnownAndSlackFilter implements Predicate { + + @Override + public boolean test(T t) { + AbstractFile af = null; + + if (t instanceof BlackboardArtifact) { + try { + af = ((BlackboardArtifact) (t)).getSleuthkitCase().getAbstractFileById(((BlackboardArtifact) t).getObjectID()); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + } else if (t instanceof AbstractFile) { + af = (AbstractFile) t; + } + + if (af != null) { + if (af.getKnown() == TskData.FileKnown.KNOWN && UserPreferences.hideKnownFilesInViewsTree()) { + return false; + } + if (af.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) && UserPreferences.hideSlackFilesInViewsTree()) { + return false; + } + } + + return true; + } +}