Merge pull request #4708 from esaunders/4910_paging_child_factory

4910 paging child factory
This commit is contained in:
Richard Cordovano 2019-05-08 14:49:12 -04:00 committed by GitHub
commit d1c3dea4f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 314 additions and 0 deletions

View File

@ -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.
*

View File

@ -0,0 +1,234 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.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 <T>
*/
public abstract class BaseChildFactory<T extends Content> extends ChildFactory.Detachable<T> {
private final Predicate<T> 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<String, EventBus> 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<T> 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<T> toPopulate) {
// For page chage events we simply return the previously calculated
// keys, otherwise we make a new set of keys.
if (!isPageChangeEvent) {
List<T> 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<List<T>> 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<T> 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<T> 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);
}
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.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<T extends Content> implements Predicate<T> {
@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;
}
}