mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-5585 non blocking project index for the FileSystemBrowser
This commit is contained in:
parent
1ef5566219
commit
a3a0870b66
4 changed files with 125 additions and 69 deletions
|
@ -23,21 +23,59 @@ import ghidra.formats.gfilesystem.FSRL;
|
|||
import ghidra.framework.main.datatable.ProjectDataTablePanel;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.datastruct.ListenerSet;
|
||||
|
||||
/**
|
||||
* An in-memory index of FSRL-to-domainfile in the current project.
|
||||
* An in-memory index of FSRL-to-domainfiles.
|
||||
*/
|
||||
public class ProjectIndexService implements DomainFolderChangeListener {
|
||||
public class ProjectIndexService implements DomainFolderChangeListener, AutoCloseable {
|
||||
|
||||
public static ProjectIndexService getInstance() {
|
||||
return SingletonHolder.instance;
|
||||
public static final ProjectIndexService DUMMY = new ProjectIndexService(null);
|
||||
|
||||
public interface ProjectIndexListener {
|
||||
void indexUpdated();
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final ProjectIndexService instance = new ProjectIndexService();
|
||||
/**
|
||||
* Returns an index for a Project. Instances returned by this method should not be
|
||||
* {@link #close() closed} by the caller.
|
||||
*
|
||||
* @param project {@link Project} to get index for, or {@code null} for a DUMMY placeholder
|
||||
* @return {@link ProjectIndexService} instance, never null
|
||||
*/
|
||||
public static synchronized ProjectIndexService getIndexFor(Project project) {
|
||||
if (project == null || project.isClosed()) {
|
||||
return DUMMY;
|
||||
}
|
||||
|
||||
ProjectIndexService result = instances.get(project.getProjectLocator());
|
||||
if (result == null) {
|
||||
result = new ProjectIndexService(project);
|
||||
instances.put(project.getProjectLocator(), result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the index instance management that a Project has been closed. Users of this service
|
||||
* need to do this because notification of Project closure is only available to GUI Plugin
|
||||
* classes.
|
||||
*
|
||||
* @param project {@link Project} that was closed
|
||||
*/
|
||||
public static synchronized void projectClosed(Project project) {
|
||||
if (project != null) {
|
||||
ProjectLocator projectLocator = project.getProjectLocator();
|
||||
ProjectIndexService result = instances.get(projectLocator);
|
||||
if (result != null) {
|
||||
instances.remove(projectLocator);
|
||||
result.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<ProjectLocator, ProjectIndexService> instances = new HashMap<>();
|
||||
|
||||
public enum IndexType {
|
||||
MD5("Executable MD5"), FSRL("FSRL");
|
||||
|
||||
|
@ -68,72 +106,85 @@ public class ProjectIndexService implements DomainFolderChangeListener {
|
|||
}
|
||||
|
||||
private Project project;
|
||||
private List<IndexInfo> indexes;
|
||||
private List<IndexInfo> indexes = List.of(new IndexInfo(IndexType.MD5, this::getMD5),
|
||||
new IndexInfo(IndexType.FSRL, this::getFSRL));
|
||||
private Thread indexThread;
|
||||
private ListenerSet<ProjectIndexListener> indexListeners =
|
||||
new ListenerSet<>(ProjectIndexListener.class, false);
|
||||
|
||||
private ProjectIndexService() {
|
||||
this.indexes = List.of(new IndexInfo(IndexType.MD5, this::getMD5),
|
||||
new IndexInfo(IndexType.FSRL, this::getFSRL));
|
||||
public ProjectIndexService(Project project) {
|
||||
this.project = project;
|
||||
|
||||
if (project != null) {
|
||||
ProjectData projectData = project.getProjectData();
|
||||
projectData.addDomainFolderChangeListener(this);
|
||||
|
||||
indexProject(projectData);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void clearProject() {
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (project != null) {
|
||||
project.getProjectData().removeDomainFolderChangeListener(this);
|
||||
for (IndexInfo index : indexes) {
|
||||
index.indexedFiles.clear();
|
||||
}
|
||||
project = null;
|
||||
indexListeners.clear();
|
||||
|
||||
Thread localIndexThread = indexThread;
|
||||
if (localIndexThread != null && localIndexThread.isAlive()) {
|
||||
localIndexThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setProject(Project newProject, TaskMonitor monitor) {
|
||||
synchronized (this) {
|
||||
if (newProject == project) {
|
||||
return;
|
||||
}
|
||||
clearProject();
|
||||
project = newProject;
|
||||
|
||||
if (project != null) {
|
||||
indexes = List.of(new IndexInfo(IndexType.MD5, this::getMD5),
|
||||
new IndexInfo(IndexType.FSRL, this::getFSRL));
|
||||
ProjectData projectData = project.getProjectData();
|
||||
projectData.removeDomainFolderChangeListener(this);
|
||||
projectData.addDomainFolderChangeListener(this);
|
||||
}
|
||||
public synchronized void addIndexListener(ProjectIndexListener listener) {
|
||||
if (project != null) {
|
||||
indexListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
if (newProject != null) {
|
||||
// index outside of sync lock to allow concurrent lookups
|
||||
indexProject(newProject.getProjectData(), monitor);
|
||||
}
|
||||
public synchronized void removeIndexListener(ProjectIndexListener listener) {
|
||||
indexListeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileAdded(DomainFile file) {
|
||||
indexFile(file);
|
||||
indexListeners.invoke().indexUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
|
||||
removeFile(fileID);
|
||||
indexListeners.invoke().indexUpdated();
|
||||
}
|
||||
|
||||
private void indexProject(ProjectData projectData, TaskMonitor monitor) {
|
||||
private void indexProject(ProjectData projectData) {
|
||||
int fileCount = projectData.getFileCount();
|
||||
if (fileCount < 0 || fileCount > ProjectDataTablePanel.MAX_FILE_COUNT) {
|
||||
return;
|
||||
}
|
||||
monitor.initialize(fileCount, "Indexing Project Metadata");
|
||||
for (DomainFile df : ProjectDataUtils.descendantFiles(projectData.getRootFolder())) {
|
||||
monitor.incrementProgress();
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
|
||||
indexThread = new Thread(() -> {
|
||||
int count = 0;
|
||||
for (DomainFile df : ProjectDataUtils.descendantFiles(projectData.getRootFolder())) {
|
||||
if (indexThread.isInterrupted()) {
|
||||
break;
|
||||
}
|
||||
indexFile(df);
|
||||
if (count++ % 10 == 0) {
|
||||
indexListeners.invoke().indexUpdated();
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
indexFile(df);
|
||||
if (monitor.getProgress() % 10 == 0) {
|
||||
Swing.allowSwingToProcessEvents();
|
||||
}
|
||||
}
|
||||
indexThread = null;
|
||||
indexListeners.invoke().indexUpdated();
|
||||
}, "Project Indexing Thread");
|
||||
indexThread.setDaemon(true);
|
||||
indexThread.start();
|
||||
}
|
||||
|
||||
private String getMD5(DomainFile file, Map<String, String> metadata) {
|
||||
|
@ -176,19 +227,23 @@ public class ProjectIndexService implements DomainFolderChangeListener {
|
|||
}
|
||||
|
||||
private synchronized void indexFile(DomainFile file) {
|
||||
String newFileId = file.getFileID();
|
||||
if (newFileId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> metadata = file.getMetadata();
|
||||
for (IndexInfo index : indexes) {
|
||||
Object indexedValue = index.mappingFunc.apply(file, metadata);
|
||||
if (indexedValue != null) {
|
||||
Object fileInfo = index.indexedFiles.get(indexedValue);
|
||||
if (fileInfo == null) {
|
||||
index.indexedFiles.put(indexedValue, file.getFileID());
|
||||
index.indexedFiles.put(indexedValue, newFileId);
|
||||
}
|
||||
else if (fileInfo instanceof List<?> fileInfoList) {
|
||||
((List<String>) fileInfoList).add(file.getFileID());
|
||||
((List<String>) fileInfoList).add(newFileId);
|
||||
}
|
||||
else if (fileInfo instanceof String prevFileId) {
|
||||
String newFileId = file.getFileID();
|
||||
if (newFileId.equals(prevFileId)) {
|
||||
// don't need to do anything
|
||||
continue;
|
||||
|
@ -205,8 +260,7 @@ public class ProjectIndexService implements DomainFolderChangeListener {
|
|||
private synchronized void removeFile(String fileId) {
|
||||
// brute force search through all entries to remove the file
|
||||
for (IndexInfo index : indexes) {
|
||||
for (Iterator<Object> it = index.indexedFiles.values().iterator(); it
|
||||
.hasNext();) {
|
||||
for (Iterator<Object> it = index.indexedFiles.values().iterator(); it.hasNext();) {
|
||||
Object fileInfo = it.next();
|
||||
if (fileInfo instanceof String fileIdStr && fileIdStr.equals(fileId)) {
|
||||
it.remove();
|
||||
|
|
|
@ -46,6 +46,7 @@ import ghidra.framework.model.*;
|
|||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugin.importer.ProjectIndexService;
|
||||
import ghidra.plugin.importer.ProjectIndexService.ProjectIndexListener;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
@ -62,12 +63,12 @@ import ghidra.util.task.TaskMonitor;
|
|||
* See the {@link FSBFileHandler} interface for how to add actions to this component.
|
||||
*/
|
||||
public class FSBComponentProvider extends ComponentProviderAdapter
|
||||
implements FileSystemEventListener, PopupActionProvider {
|
||||
implements FileSystemEventListener, PopupActionProvider, ProjectIndexListener {
|
||||
private static final String TITLE = "Filesystem Viewer";
|
||||
|
||||
private FSBIcons fsbIcons = FSBIcons.getInstance();
|
||||
private FileSystemService fsService = FileSystemService.getInstance();
|
||||
private ProjectIndexService projectIndex = ProjectIndexService.getInstance();
|
||||
private ProjectIndexService projectIndex = ProjectIndexService.DUMMY;
|
||||
|
||||
private FileSystemBrowserPlugin plugin;
|
||||
private GTree gTree;
|
||||
|
@ -169,7 +170,7 @@ public class FSBComponentProvider extends ComponentProviderAdapter
|
|||
if (df != null) {
|
||||
overlays.add(FSBIcons.IMPORTED_OVERLAY_ICON);
|
||||
|
||||
if (plugin.isOpen(df)) {
|
||||
if (df.isOpen()) {
|
||||
// TODO: change this to a OVERLAY_OPEN option when fetching icon
|
||||
setForeground(selected ? Palette.CYAN : Palette.MAGENTA);
|
||||
}
|
||||
|
@ -222,6 +223,7 @@ public class FSBComponentProvider extends ComponentProviderAdapter
|
|||
|
||||
void dispose() {
|
||||
plugin.getTool().removePopupActionProvider(this);
|
||||
projectIndex.removeIndexListener(this);
|
||||
|
||||
if (rootNode != null && rootNode.getFSRef() != null && !rootNode.getFSRef().isClosed()) {
|
||||
rootNode.getFSRef().getFilesystem().getRefManager().removeListener(this);
|
||||
|
@ -235,6 +237,7 @@ public class FSBComponentProvider extends ComponentProviderAdapter
|
|||
rootNode = null;
|
||||
plugin = null;
|
||||
gTree = null;
|
||||
projectIndex = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -265,12 +268,18 @@ public class FSBComponentProvider extends ComponentProviderAdapter
|
|||
}
|
||||
|
||||
public void setProject(Project project) {
|
||||
gTree.runTask(monitor -> {
|
||||
projectIndex.setProject(project, monitor);
|
||||
Swing.runLater(() -> {
|
||||
projectIndex = ProjectIndexService.getIndexFor(project);
|
||||
projectIndex.addIndexListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void indexUpdated() {
|
||||
// icons might need repainting after new info is available
|
||||
Swing.runLater(() -> {
|
||||
if (gTree != null) {
|
||||
contextChanged();
|
||||
gTree.repaint();
|
||||
}); // icons might need repainting after new info is available
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,8 @@ import ghidra.app.services.FileSystemBrowserService;
|
|||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.ApplicationLevelPlugin;
|
||||
import ghidra.framework.main.FrontEndService;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.model.ProjectListener;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
|
@ -295,7 +296,7 @@ public class FileSystemBrowserPlugin extends Plugin
|
|||
if (FileSystemService.isInitialized()) {
|
||||
fsService().closeUnusedFileSystems();
|
||||
}
|
||||
ProjectIndexService.getInstance().clearProject();
|
||||
ProjectIndexService.projectClosed(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -305,16 +306,6 @@ public class FileSystemBrowserPlugin extends Plugin
|
|||
// to tell them about the new project
|
||||
}
|
||||
|
||||
public boolean isOpen(DomainFile df) {
|
||||
Object tmp = new Object();
|
||||
DomainObject openDF = df.getOpenedDomainObject(tmp);
|
||||
if (openDF != null) {
|
||||
openDF.release(tmp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public File getLastExportDirectory() {
|
||||
return lastExportDirectory != null
|
||||
? lastExportDirectory
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -36,10 +36,12 @@ import ghidra.util.task.TaskMonitor;
|
|||
public class GFileSystemLoadKernelTask extends Task {
|
||||
private List<FSRL> fileList;
|
||||
private ProgramManager programManager;
|
||||
private Project project;
|
||||
|
||||
public GFileSystemLoadKernelTask(Plugin plugin, ProgramManager programManager,
|
||||
List<FSRL> fileList) {
|
||||
super("Loading iOS kernel...", true, true, true);
|
||||
this.project = plugin.getTool().getProject();
|
||||
this.programManager = programManager;
|
||||
this.fileList = fileList;
|
||||
}
|
||||
|
@ -114,7 +116,7 @@ public class GFileSystemLoadKernelTask extends Task {
|
|||
}
|
||||
monitor.setMessage("Opening " + file.getName());
|
||||
|
||||
ProjectIndexService projectIndex = ProjectIndexService.getInstance();
|
||||
ProjectIndexService projectIndex = ProjectIndexService.getIndexFor(project);
|
||||
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
|
||||
if ( existingDF != null && programManager != null ) {
|
||||
programManager.openProgram(existingDF);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue