mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
GT-2861 - Task Monitor - removed recently added Task Monitor Service due
to conceptual issues; fixed bug in TaskLauncher that caused deadlock; updated ImporterUtilities usage of TaskLauncher to trigger task dialog
This commit is contained in:
parent
2108a5ed4c
commit
a94c6efe68
21 changed files with 413 additions and 1452 deletions
|
@ -1,75 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
//Imports the code segment of a set of binaries
|
||||
|
||||
import ghidra.app.plugin.core.script.Ingredient;
|
||||
import ghidra.app.plugin.core.script.IngredientDescription;
|
||||
import ghidra.app.script.GatherParamPanel;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ImportCodeSegment extends GhidraScript implements Ingredient {
|
||||
|
||||
/**
|
||||
* @see ghidra.app.script.GhidraScript#run()
|
||||
*/
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
IngredientDescription[] ingredients = getIngredientDescriptions();
|
||||
for (int i = 0; i < ingredients.length; i++) {
|
||||
state.addParameter(ingredients[i].getID(), ingredients[i].getLabel(),
|
||||
ingredients[i].getType(), ingredients[i].getDefaultValue());
|
||||
}
|
||||
if (!state.displayParameterGatherer("Script Options")) {
|
||||
return;
|
||||
}
|
||||
File file = (File) state.getEnvironmentVar("CodeSegmentBinary");
|
||||
LanguageID languageID = (LanguageID) state.getEnvironmentVar("LanguageID");
|
||||
CompilerSpecID compilerSpecID = (CompilerSpecID) state.getEnvironmentVar("CompilerSpecID");
|
||||
|
||||
Program prog = null;
|
||||
Language lang = getLanguage(languageID);
|
||||
if (lang == null) {
|
||||
println("Unable to locate default language for " + languageID);
|
||||
throw new Exception("Unable to locate default language for " + languageID);
|
||||
}
|
||||
CompilerSpec compilerSpec = lang.getCompilerSpecByID(compilerSpecID);
|
||||
if (compilerSpec == null) {
|
||||
compilerSpec = lang.getDefaultCompilerSpec();
|
||||
}
|
||||
prog = importFileAsBinary(file, lang, compilerSpec);
|
||||
if (prog == null) {
|
||||
println("Unable to import program from file " + file.getName());
|
||||
throw new Exception("Unable to import program from file " + file.getName());
|
||||
}
|
||||
state.setCurrentProgram(prog);
|
||||
openProgram(prog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IngredientDescription[] getIngredientDescriptions() {
|
||||
IngredientDescription[] retVal =
|
||||
new IngredientDescription[] {
|
||||
new IngredientDescription("CodeSegmentBinary",
|
||||
"File containing blk 0 code segment:", GatherParamPanel.FILE, ""),
|
||||
new IngredientDescription("LanguageID", "Language:", GatherParamPanel.LANGUAGE, "") };
|
||||
return retVal;
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* NOTE: again, VERSIONTRACKING?
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
//Import XML Directory Hierarchy
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
import ghidra.app.plugin.core.script.Ingredient;
|
||||
import ghidra.app.plugin.core.script.IngredientDescription;
|
||||
import ghidra.app.script.GatherParamPanel;
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.app.util.importer.*;
|
||||
import ghidra.app.util.opinion.XmlLoader;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.util.DefaultLanguageService;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ImportHierarchicalXML extends GhidraScript implements Ingredient {
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
IngredientDescription[] ingredients = getIngredientDescriptions();
|
||||
for (IngredientDescription ingredient : ingredients) {
|
||||
state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
|
||||
ingredient.getDefaultValue());
|
||||
}
|
||||
if (!state.displayParameterGatherer("Script Options")) {
|
||||
return;
|
||||
}
|
||||
File topLevel = (File) state.getEnvironmentVar("XMLTopLevelDir");
|
||||
if (!topLevel.exists()) {
|
||||
throw new Exception("Directory " + topLevel.toString() + " does not exist!");
|
||||
}
|
||||
if (!topLevel.isDirectory()) {
|
||||
throw new Exception(topLevel.toString() + " is not a directory!");
|
||||
}
|
||||
|
||||
Project project = state.getProject();
|
||||
addObject(project.getProjectData().getRootFolder(), topLevel);
|
||||
project.releaseFiles(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IngredientDescription[] getIngredientDescriptions() {
|
||||
IngredientDescription[] retVal = new IngredientDescription[] {
|
||||
new IngredientDescription("XMLTopLevelDir", "Top Level Directory Containing XML Files:",
|
||||
GatherParamPanel.DIRECTORY, ""),
|
||||
new IngredientDescription("XMLImportLanguageID", "Language ID (you don't know this):",
|
||||
GatherParamPanel.STRING, ""),
|
||||
new IngredientDescription("XMLImportCompilerSpecID",
|
||||
"Compiler Spec ID (again, you don't know this):", GatherParamPanel.STRING, "") };
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void addObject(DomainFolder parentFolder, File obj) throws Exception {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (obj.isDirectory()) {
|
||||
DomainFolder df = parentFolder.createFolder(obj.getName());
|
||||
File[] files = obj.listFiles(new XMLFileFilter());
|
||||
for (File file : files) {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
addObject(df, file);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LanguageID languageID =
|
||||
new LanguageID((String) state.getEnvironmentVar("XMLImportLanguageID"));
|
||||
CompilerSpecID compilerSpecID =
|
||||
new CompilerSpecID((String) state.getEnvironmentVar("XMLImportCompilerSpecID"));
|
||||
Language language = DefaultLanguageService.getLanguageService().getLanguage(languageID);
|
||||
CompilerSpec compilerSpec = language.getCompilerSpecByID(compilerSpecID);
|
||||
println("Importing " + obj.toString());
|
||||
MessageLog messageLog = new MessageLog();
|
||||
try {
|
||||
String programNameOverride = null;
|
||||
AutoImporter.importFresh(obj, parentFolder, this, messageLog, monitor,
|
||||
new SingleLoaderFilter(XmlLoader.class),
|
||||
new LcsHintLoadSpecChooser(language, compilerSpec), programNameOverride,
|
||||
OptionChooser.DEFAULT_OPTIONS, MultipleProgramsStrategy.ALL_PROGRAMS);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Error importing " + obj + ": " + messageLog, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class XMLFileFilter implements FilenameFilter {
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
File newFile = new File(dir, name);
|
||||
if (newFile.isDirectory() && hasXML(newFile)) {
|
||||
return true;
|
||||
}
|
||||
if (name.endsWith(".xml")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasXML(File file) {
|
||||
File[] files = file.listFiles();
|
||||
for (File file2 : files) {
|
||||
if (file2.isDirectory() && hasXML(file2)) {
|
||||
return true;
|
||||
}
|
||||
if (file2.getName().endsWith(".xml")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -40,7 +40,6 @@ public class QueryOpinionService {
|
|||
|
||||
List<ResourceFile> files = searchAndFindAllOpinionXMLs();
|
||||
for (ResourceFile file : files) {
|
||||
Msg.debug(QueryOpinionService.class, "parsing " + file);
|
||||
try {
|
||||
parseFile(file);
|
||||
}
|
||||
|
|
|
@ -33,13 +33,14 @@ import ghidra.program.model.listing.Program;
|
|||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.*;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class OpenProgramTask extends Task {
|
||||
|
||||
private final List<DomainFileInfo> domainFileInfoList = new ArrayList<>();
|
||||
private List<Program> programList = new ArrayList<>();
|
||||
private TaskMonitor monitor;
|
||||
|
||||
private final Object consumer;
|
||||
private boolean silent; // if true operation does not permit interaction
|
||||
private boolean noCheckout; // if true operation should not perform optional checkout
|
||||
|
@ -122,20 +123,17 @@ public class OpenProgramTask extends Task {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor taskMonitor) {
|
||||
this.monitor = TaskMonitorService.getMonitor();
|
||||
public void run(TaskMonitor monitor) {
|
||||
|
||||
if (domainFileInfoList.size() > 1) {
|
||||
monitor.initialize(domainFileInfoList.size());
|
||||
}
|
||||
taskMonitor.initialize(domainFileInfoList.size());
|
||||
|
||||
for (DomainFileInfo domainFileInfo : domainFileInfoList) {
|
||||
if (monitor.isCancelled()) {
|
||||
if (taskMonitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
openDomainFile(domainFileInfo);
|
||||
|
||||
monitor.incrementProgress(1);
|
||||
taskMonitor.incrementProgress(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,12 +152,12 @@ public class OpenProgramTask extends Task {
|
|||
}
|
||||
|
||||
private void openReadOnlyFile(DomainFile domainFile, int version) {
|
||||
monitor.setMessage("Opening " + domainFile.getName());
|
||||
taskMonitor.setMessage("Opening " + domainFile.getName());
|
||||
openReadOnly(domainFile, version);
|
||||
}
|
||||
|
||||
private void openVersionedFile(DomainFile domainFile, int version) {
|
||||
monitor.setMessage("Getting Version " + version + " for " + domainFile.getName());
|
||||
taskMonitor.setMessage("Getting Version " + version + " for " + domainFile.getName());
|
||||
openReadOnly(domainFile, version);
|
||||
}
|
||||
|
||||
|
@ -168,7 +166,7 @@ public class OpenProgramTask extends Task {
|
|||
try {
|
||||
contentType = domainFile.getContentType();
|
||||
Program program =
|
||||
(Program) domainFile.getReadOnlyDomainObject(consumer, version, monitor);
|
||||
(Program) domainFile.getReadOnlyDomainObject(consumer, version, taskMonitor);
|
||||
|
||||
if (program == null) {
|
||||
String errorMessage = "Can't open program - \"" + domainFile.getPathname() + "\"";
|
||||
|
@ -203,7 +201,7 @@ public class OpenProgramTask extends Task {
|
|||
|
||||
private void openUnversionedFile(DomainFile domainFile) {
|
||||
String filename = domainFile.getName();
|
||||
monitor.setMessage("Opening " + filename);
|
||||
taskMonitor.setMessage("Opening " + filename);
|
||||
performOptionalCheckout(domainFile);
|
||||
try {
|
||||
openFileMaybeUgrade(domainFile);
|
||||
|
@ -241,7 +239,7 @@ public class OpenProgramTask extends Task {
|
|||
|
||||
try {
|
||||
Program program =
|
||||
(Program) domainFile.getDomainObject(consumer, false, recoverFile, monitor);
|
||||
(Program) domainFile.getDomainObject(consumer, false, recoverFile, taskMonitor);
|
||||
|
||||
if (program != null) {
|
||||
programList.add(program);
|
||||
|
@ -251,7 +249,7 @@ public class OpenProgramTask extends Task {
|
|||
catch (VersionException e) {
|
||||
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, openPromptText, e)) {
|
||||
Program program =
|
||||
(Program) domainFile.getDomainObject(consumer, true, recoverFile, monitor);
|
||||
(Program) domainFile.getDomainObject(consumer, true, recoverFile, taskMonitor);
|
||||
if (program != null) {
|
||||
programList.add(program);
|
||||
}
|
||||
|
@ -284,8 +282,8 @@ public class OpenProgramTask extends Task {
|
|||
CheckoutDialog dialog = new CheckoutDialog(domainFile, user);
|
||||
if (dialog.showDialog() == CheckoutDialog.CHECKOUT) {
|
||||
try {
|
||||
monitor.setMessage("Checking Out " + domainFile.getName());
|
||||
if (domainFile.checkout(dialog.exclusiveCheckout(), monitor)) {
|
||||
taskMonitor.setMessage("Checking Out " + domainFile.getName());
|
||||
if (domainFile.checkout(dialog.exclusiveCheckout(), taskMonitor)) {
|
||||
return;
|
||||
}
|
||||
Msg.showError(this, null, "Checkout Failed", "Exclusive checkout failed for: " +
|
||||
|
|
|
@ -102,8 +102,8 @@ public class AddToProgramDialog extends ImporterDialog {
|
|||
options = selectedLoader.getDefaultOptions(byteProvider, selectedLoadSpec, null, true);
|
||||
}
|
||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
||||
ImporterUtilities.doAddToProgram(fsrl, selectedLoadSpec, options, addToProgram, monitor,
|
||||
tool);
|
||||
ImporterUtilities.addContentToProgram(tool, addToProgram, fsrl, selectedLoadSpec, options,
|
||||
monitor);
|
||||
});
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -348,8 +348,8 @@ public class ImporterDialog extends DialogComponentProvider {
|
|||
String programName = FilenameUtils.getName(programPath);
|
||||
List<Option> localOptions = getOptions(loadSpec);
|
||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
||||
ImporterUtilities.doSingleImport(fsrl, importFolder, loadSpec, programName,
|
||||
localOptions, tool, programManager, monitor);
|
||||
ImporterUtilities.importSingleFile(tool, programManager, fsrl, importFolder,
|
||||
loadSpec, programName, localOptions, monitor);
|
||||
});
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -67,9 +67,9 @@ public class ImporterPlugin extends Plugin
|
|||
implements FileImporterService, FrontEndable, ProjectListener {
|
||||
|
||||
private static final String IMPORT_MENU_GROUP = "Import";
|
||||
private static final String ADD_TO_PROGRAM_MENU_GROUP = "Import: Add To Program";
|
||||
static final String IMPORTER_PLUGIN_DESC =
|
||||
"This plugin manages importing files, including those contained within firmware/filesystem images.";
|
||||
"This plugin manages importing files, including those contained within " +
|
||||
"firmware/filesystem images.";
|
||||
|
||||
private DockingAction importAction;
|
||||
private DockingAction importSelectionAction;// NA in front-end
|
||||
|
@ -165,9 +165,10 @@ public class ImporterPlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public void importFile(DomainFolder folder, File file) {
|
||||
|
||||
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
||||
ImporterUtilities.showImportDialog(fsrl, folder, null, getTool(),
|
||||
getTool().getService(ProgramManager.class));
|
||||
ProgramManager manager = tool.getService(ProgramManager.class);
|
||||
ImporterUtilities.showImportDialog(tool, manager, fsrl, folder, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -362,55 +363,50 @@ public class ImporterPlugin extends Plugin
|
|||
Program program = manager.getCurrentProgram();
|
||||
|
||||
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
||||
TaskLauncher.launchNonModal("Show Add To Program Dialog", monitor -> {
|
||||
TaskLauncher.launchModal("Show Add To Program Dialog", monitor -> {
|
||||
ImporterUtilities.showAddToProgramDialog(fsrl, program, tool, monitor);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a selection of bytes from the listing window.
|
||||
*/
|
||||
protected void doImportSelectionAction(ProgramSelection selection) {
|
||||
if (selection != null && selection.getNumAddressRanges() == 1) {
|
||||
AddressRange range = selection.getFirstRange();// should only be 1
|
||||
if (range.getLength() < (Integer.MAX_VALUE & 0xffffffffL)) {
|
||||
ProgramManager programManager = tool.getService(ProgramManager.class);
|
||||
Program program = programManager.getCurrentProgram();
|
||||
if (program != null) {
|
||||
Memory memory = program.getMemory();
|
||||
MemoryBlock block = memory.getBlock(range.getMinAddress());
|
||||
String tempFileName = block.getName() + "_[" + range.getMinAddress() + "," +
|
||||
range.getMaxAddress() + "]_";
|
||||
try {
|
||||
File tempFile = File.createTempFile(tempFileName, ".tmp");
|
||||
OutputStream outputStream = new FileOutputStream(tempFile);
|
||||
try {
|
||||
byte[] bytes = new byte[(int) range.getLength()];
|
||||
memory.getBytes(range.getMinAddress(), bytes);
|
||||
outputStream.write(bytes);
|
||||
}
|
||||
finally {
|
||||
outputStream.close();
|
||||
}
|
||||
DomainFolder folder =
|
||||
AppInfo.getActiveProject().getProjectData().getRootFolder();
|
||||
importFile(folder, tempFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showInfo(getClass(), tool.getActiveWindow(), "I/O Error Occurred",
|
||||
e.getMessage());
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
Msg.showInfo(getClass(), tool.getActiveWindow(),
|
||||
"Memory Access Error Occurred", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Msg.showInfo(getClass(), tool.getActiveWindow(), "Selection Too Large",
|
||||
"The selection is too large to extract.");
|
||||
if (selection == null || selection.getNumAddressRanges() != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddressRange range = selection.getFirstRange();// should only be 1
|
||||
if (range.getLength() >= (Integer.MAX_VALUE & 0xffffffffL)) {
|
||||
Msg.showInfo(getClass(), tool.getActiveWindow(), "Selection Too Large",
|
||||
"The selection is too large to extract.");
|
||||
return;
|
||||
}
|
||||
|
||||
ProgramManager programManager = tool.getService(ProgramManager.class);
|
||||
Program program = programManager.getCurrentProgram();
|
||||
if (program == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Memory memory = program.getMemory();
|
||||
MemoryBlock block = memory.getBlock(range.getMinAddress());
|
||||
String tempFileName =
|
||||
block.getName() + "_[" + range.getMinAddress() + "," + range.getMaxAddress() + "]_";
|
||||
try {
|
||||
File tempFile = File.createTempFile(tempFileName, ".tmp");
|
||||
try (OutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||
byte[] bytes = new byte[(int) range.getLength()];
|
||||
memory.getBytes(range.getMinAddress(), bytes);
|
||||
outputStream.write(bytes);
|
||||
}
|
||||
|
||||
DomainFolder folder = AppInfo.getActiveProject().getProjectData().getRootFolder();
|
||||
importFile(folder, tempFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "I/O Error Occurred", e.getMessage(), e);
|
||||
}
|
||||
catch (MemoryAccessException e) {
|
||||
Msg.showError(this, null, "Memory Access Error Occurred", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
*/
|
||||
package ghidra.plugin.importer;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Window;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
|
||||
|
@ -49,6 +48,12 @@ import ghidra.util.task.TaskLauncher;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Utilities for importing files.
|
||||
*
|
||||
* <p>Note: if a method takes a {@link TaskMonitor}, then that method should only be called
|
||||
* from a background task.
|
||||
*/
|
||||
public class ImporterUtilities {
|
||||
|
||||
/**
|
||||
|
@ -90,14 +95,17 @@ public class ImporterUtilities {
|
|||
*/
|
||||
public static void setProgramProperties(Program program, FSRL fsrl, TaskMonitor monitor)
|
||||
throws CancelledException, IOException {
|
||||
|
||||
Objects.requireNonNull(monitor);
|
||||
|
||||
int id = program.startTransaction("setImportProperties");
|
||||
try {
|
||||
fsrl = FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
||||
|
||||
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
||||
propertyList.setString(ProgramMappingService.PROGRAM_SOURCE_FSRL, fsrl.toString());
|
||||
if ((program.getExecutableMD5() == null || program.getExecutableMD5().isEmpty()) &&
|
||||
fsrl.getMD5() != null) {
|
||||
String md5 = program.getExecutableMD5();
|
||||
if ((md5 == null || md5.isEmpty()) && fsrl.getMD5() != null) {
|
||||
program.setExecutableMD5(fsrl.getMD5());
|
||||
}
|
||||
}
|
||||
|
@ -114,121 +122,156 @@ public class ImporterUtilities {
|
|||
* <p>
|
||||
* If the file is a container of other files, a batch import dialog will be used,
|
||||
* otherwise the normal single file import dialog will be shown.
|
||||
* <p>
|
||||
*
|
||||
* @param fsrl {@link FSRL} of the file to import.
|
||||
* @param destFolder {@link DomainFolder} destination folder where the imported file
|
||||
* will default to. (the user will be able to choose a different location).
|
||||
* @param suggestedDestinationPath optional string path that will automatically be pre-pended
|
||||
* to the destination filename.
|
||||
* @param tool {@link PluginTool} will be used as the parent tool for dialogs.
|
||||
* @param programManager optional {@link ProgramManager} instance to use to open imported binaries with, or null.
|
||||
* @param tool {@link PluginTool} will be used as the parent tool for dialogs
|
||||
* @param programManager optional {@link ProgramManager} instance to use to open imported
|
||||
* binaries with, or null
|
||||
* @param fsrl {@link FSRL} of the file to import
|
||||
* @param destinationFolder {@link DomainFolder} destination folder where the imported file
|
||||
* will default to. (the user will be able to choose a different location)
|
||||
* @param suggestedPath optional string path that will automatically be pre-pended
|
||||
* to the destination filename
|
||||
*/
|
||||
public static void showImportDialog(FSRL fsrl, DomainFolder destFolder,
|
||||
String suggestedDestinationPath, PluginTool tool, ProgramManager programManager) {
|
||||
public static void showImportDialog(PluginTool tool, ProgramManager programManager, FSRL fsrl,
|
||||
DomainFolder destinationFolder, String suggestedPath) {
|
||||
|
||||
Component parent = tool.getActiveWindow();
|
||||
AtomicReference<RefdFile> refdFile = new AtomicReference<>();
|
||||
AtomicReference<FSRL> fqFSRL = new AtomicReference<>();
|
||||
AtomicBoolean isFSContainer = new AtomicBoolean();
|
||||
AtomicBoolean success = new AtomicBoolean();
|
||||
TaskLauncher.launchModal("Import", monitor -> {
|
||||
showImportDialog(tool, programManager, fsrl, destinationFolder, suggestedPath, monitor);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the appropriate import dialog for the specified {@link FSRL file}.
|
||||
* <p>
|
||||
* If the file is a container of other files, a batch import dialog will be used,
|
||||
* otherwise the normal single file import dialog will be shown.]
|
||||
* <p>
|
||||
* If you are not in a monitored task, then call
|
||||
* {@link #showImportDialog(PluginTool, ProgramManager, FSRL, DomainFolder, String)}.
|
||||
*
|
||||
* @param tool {@link PluginTool} will be used as the parent tool for dialogs
|
||||
* @param programManager optional {@link ProgramManager} instance to use to open imported
|
||||
* binaries with, or null
|
||||
* @param fsrl {@link FSRL} of the file to import
|
||||
* @param destinationFolder {@link DomainFolder} destination folder where the imported file
|
||||
* will default to. (the user will be able to choose a different location)
|
||||
* @param suggestedPath optional string path that will automatically be pre-pended
|
||||
* to the destination filename
|
||||
* @param monitor the task monitor to use for monitoring; cannot be null
|
||||
*/
|
||||
public static void showImportDialog(PluginTool tool, ProgramManager programManager, FSRL fsrl,
|
||||
DomainFolder destinationFolder, String suggestedPath, TaskMonitor monitor) {
|
||||
|
||||
Objects.requireNonNull(monitor);
|
||||
|
||||
RefdFile referencedFile = null;
|
||||
try {
|
||||
// do IO stuff in separate thread, with no UI prompts
|
||||
TaskLauncher.launchModal("Import", (monitor) -> {
|
||||
try {
|
||||
RefdFile tmpRefdFile =
|
||||
FileSystemService.getInstance().getRefdFile(fsrl, monitor);
|
||||
refdFile.set(tmpRefdFile);
|
||||
FileSystemService service = FileSystemService.getInstance();
|
||||
referencedFile = service.getRefdFile(fsrl, monitor);
|
||||
|
||||
FSRL tmpFQFSRL =
|
||||
FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
||||
fqFSRL.set(tmpFQFSRL);
|
||||
|
||||
isFSContainer.set(FileSystemService.getInstance().isFileFilesystemContainer(
|
||||
tmpFQFSRL, monitor));
|
||||
|
||||
success.set(true);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.showError(ImporterUtilities.class, parent, "Import Error",
|
||||
"Unable to import file " + fsrl.getName() +
|
||||
(ioe.getMessage() != null ? ("\n\nCause: " + ioe.getMessage()) : ""),
|
||||
ioe);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.info(ImporterUtilities.class, "ShowImportDialog canceled");
|
||||
}
|
||||
});
|
||||
|
||||
if (!success.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refdFile.get().file.getLength() == 0) {
|
||||
Msg.showError(ImporterUtilities.class, parent, "File is empty",
|
||||
FSRL fullFsrl = service.getFullyQualifiedFSRL(fsrl, monitor);
|
||||
boolean isFSContainer = service.isFileFilesystemContainer(fullFsrl, monitor);
|
||||
if (referencedFile.file.getLength() == 0) {
|
||||
Msg.showError(ImporterUtilities.class, null, "File is empty",
|
||||
"File " + fsrl.getPath() + " is empty, nothing to import");
|
||||
return;
|
||||
}
|
||||
|
||||
GFileSystem fs = refdFile.get().fsRef.getFilesystem();
|
||||
GFileSystem fs = referencedFile.fsRef.getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider) {
|
||||
doFSImport(refdFile.get(), fqFSRL.get(), destFolder, programManager, tool);
|
||||
doFsImport(referencedFile, fullFsrl, destinationFolder, programManager, tool);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFSContainer.get()) {
|
||||
// If file was a container,
|
||||
// allow the user to pick between single import, batch import and fs browser
|
||||
FileSystemBrowserService fsbService =
|
||||
tool.getService(FileSystemBrowserService.class);
|
||||
String fsbChoice = (fsbService != null) ? "File system" : null;
|
||||
int choice = OptionDialog.showOptionDialog(parent, "Container file detected",
|
||||
"The file " + refdFile.get().file.getName() +
|
||||
" seems to have nested files in it. Select an import mode:",
|
||||
"Single file", "Batch", fsbChoice, OptionDialog.QUESTION_MESSAGE);
|
||||
if (choice == 1) {
|
||||
importSingleFile(fqFSRL.get(), destFolder, suggestedDestinationPath, tool,
|
||||
programManager);
|
||||
}
|
||||
else if (choice == 2) {
|
||||
BatchImportDialog.showAndImport(tool, null, Arrays.asList(fqFSRL.get()),
|
||||
destFolder, programManager);
|
||||
}
|
||||
else if (choice == 3) {
|
||||
fsbService.openFileSystem(fqFSRL.get());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If file was normal,
|
||||
// do a normal single-file import
|
||||
importSingleFile(fqFSRL.get(), destFolder, suggestedDestinationPath, tool,
|
||||
programManager);
|
||||
if (!isFSContainer) {
|
||||
// normal file; do a single-file import
|
||||
importSingleFile(fullFsrl, destinationFolder, suggestedPath, tool, programManager,
|
||||
monitor);
|
||||
return;
|
||||
}
|
||||
|
||||
// file is a container, ask user to pick single import, batch import or fs browser
|
||||
importFromContainer(tool, programManager, destinationFolder, suggestedPath, monitor,
|
||||
referencedFile, fullFsrl);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
String message = ioe.getMessage();
|
||||
Msg.showError(ImporterUtilities.class, null, "Import Error", "Unable to import file " +
|
||||
fsrl.getName() + (message != null ? ("\n\nCause: " + message) : ""), ioe);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.info(ImporterUtilities.class, "Show Import Dialog canceled");
|
||||
}
|
||||
finally {
|
||||
if (refdFile.get() != null) {
|
||||
try {
|
||||
refdFile.get().close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
close(referencedFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(Closeable c) {
|
||||
if (c != null) {
|
||||
try {
|
||||
c.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void importFromContainer(PluginTool tool, ProgramManager programManager,
|
||||
DomainFolder destinationFolder, String suggestedPath, TaskMonitor monitor,
|
||||
RefdFile referencedFile, FSRL fullFsrl) {
|
||||
|
||||
Window parent = tool.getActiveWindow();
|
||||
FileSystemBrowserService fsbService = tool.getService(FileSystemBrowserService.class);
|
||||
int choice = 0; // cancelled
|
||||
if (fsbService == null) {
|
||||
|
||||
//@formatter:off
|
||||
choice = OptionDialog.showOptionDialog(parent, "Container File Detected",
|
||||
"The file " + referencedFile.file.getName() +
|
||||
" seems to have nested files in it. Select an import mode:",
|
||||
"Single file", // 1
|
||||
"Batch", // 2
|
||||
OptionDialog.QUESTION_MESSAGE);
|
||||
}
|
||||
else {
|
||||
choice = OptionDialog.showOptionDialog(parent, "Container File Detected",
|
||||
"The file " + referencedFile.file.getName() +
|
||||
" seems to have nested files in it. Select an import mode:",
|
||||
"Single file", // 1
|
||||
"Batch", // 2
|
||||
"File System", // 3
|
||||
OptionDialog.QUESTION_MESSAGE);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
if (choice == 1) {
|
||||
importSingleFile(fullFsrl, destinationFolder, suggestedPath, tool, programManager,
|
||||
monitor);
|
||||
}
|
||||
else if (choice == 2) {
|
||||
BatchImportDialog.showAndImport(tool, null, Arrays.asList(fullFsrl), destinationFolder,
|
||||
programManager);
|
||||
}
|
||||
else if (choice == 3) {
|
||||
fsbService.openFileSystem(fullFsrl);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showAddToProgramDialog(FSRL fsrl, Program program, PluginTool tool,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
Objects.requireNonNull(monitor);
|
||||
|
||||
try {
|
||||
ByteProvider provider = FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
||||
|
||||
if (provider.length() == 0) {
|
||||
Msg.showWarn(null, null, "Error opening " + fsrl.getName(),
|
||||
"The item does not correspond to a valid file.");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Loader, Collection<LoadSpec>> loadMap =
|
||||
LoaderService.getAllSupportedLoadSpecs(provider);
|
||||
|
||||
|
@ -239,7 +282,7 @@ public class ImporterUtilities {
|
|||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(ImporterUtilities.class, null, "Error reading data",
|
||||
Msg.showError(ImporterUtilities.class, null, "Error Reading Resource",
|
||||
"I/O error reading " + fsrl.getName(), e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
|
@ -250,48 +293,46 @@ public class ImporterUtilities {
|
|||
|
||||
/**
|
||||
* Constructs a {@link ImporterDialog} and shows it in the swing thread.
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* @param fsrl the file system resource locater (can be a simple file or an element of
|
||||
* a more complex file such as a zip file)
|
||||
* @param defaultFolder the default destination folder for the imported file. Can be null.
|
||||
* @param suggestedDestinationPath optional string path that will automatically be pre-pended
|
||||
* to the destination filename.
|
||||
* a more complex file such as a zip file)
|
||||
* @param destinationFolder the default destination folder for the imported file. Can be null
|
||||
* @param suggestedPath optional string path that will automatically be pre-pended
|
||||
* to the destination filename
|
||||
* @param tool the parent UI component
|
||||
* @param programManager optional {@link ProgramManager} instance to open the imported file in.
|
||||
* @throws IOException if there was an IO-related issue importing the file.
|
||||
* @throws CancelledException if the import was canceled.
|
||||
* @param programManager optional {@link ProgramManager} instance to open the imported file in
|
||||
*/
|
||||
private static void importSingleFile(FSRL fsrl, DomainFolder defaultFolder,
|
||||
String suggestedDestinationPath, PluginTool tool, ProgramManager programManager) {
|
||||
private static void importSingleFile(FSRL fsrl, DomainFolder destinationFolder,
|
||||
String suggestedPath, PluginTool tool, ProgramManager programManager,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
||||
try {
|
||||
ByteProvider provider =
|
||||
FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
||||
Map<Loader, Collection<LoadSpec>> loadMap =
|
||||
LoaderService.getAllSupportedLoadSpecs(provider);
|
||||
try {
|
||||
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
ImporterDialog importerDialog = new ImporterDialog(tool, programManager,
|
||||
loadMap, provider, suggestedDestinationPath);
|
||||
if (defaultFolder != null) {
|
||||
importerDialog.setDestinationFolder(defaultFolder);
|
||||
}
|
||||
tool.showDialog(importerDialog);
|
||||
});
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.showError(ImporterUtilities.class, tool.getActiveWindow(),
|
||||
"Error Importing File", "Error when importing file " + fsrl, ioe);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.info(ImporterUtilities.class, "Import single file " + fsrl + " cancelled");
|
||||
}
|
||||
});
|
||||
ByteProvider provider = FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
||||
Map<Loader, Collection<LoadSpec>> loadMap =
|
||||
LoaderService.getAllSupportedLoadSpecs(provider);
|
||||
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
ImporterDialog importerDialog =
|
||||
new ImporterDialog(tool, programManager, loadMap, provider, suggestedPath);
|
||||
if (destinationFolder != null) {
|
||||
importerDialog.setDestinationFolder(destinationFolder);
|
||||
}
|
||||
|
||||
tool.showDialog(importerDialog);
|
||||
});
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Msg.showError(ImporterUtilities.class, tool.getActiveWindow(), "Error Importing File",
|
||||
"Error when importing file " + fsrl, ioe);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
Msg.info(ImporterUtilities.class, "Import single file " + fsrl + " cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
private static void doFSImport(RefdFile refdFile, FSRL fsrl, DomainFolder destFolderParam,
|
||||
private static void doFsImport(RefdFile refdFile, FSRL fsrl, DomainFolder destFolderParam,
|
||||
ProgramManager programManager, PluginTool tool) {
|
||||
TaskLauncher.launchNonModal("Import File (FileSystem specific)", monitor -> {
|
||||
GFile gfile = refdFile.file;
|
||||
|
@ -339,20 +380,21 @@ public class ImporterUtilities {
|
|||
|
||||
/**
|
||||
* Perform file import and open using optional programManager
|
||||
* @param tool tool to which popup dialogs should be associated
|
||||
* @param programManager program manager to open imported file with or null
|
||||
* @param fsrl import file location
|
||||
* @param destFolder project destination folder
|
||||
* @param loadSpec import {@link LoadSpec}
|
||||
* @param programName program name
|
||||
* @param options import options
|
||||
* @param tool tool to which popup dialogs should be associated
|
||||
* @param programManager program manager to open imported file with or null
|
||||
* @param monitor task monitor
|
||||
*/
|
||||
public static void doSingleImport(FSRL fsrl, DomainFolder destFolder, LoadSpec loadSpec,
|
||||
String programName, List<Option> options, PluginTool tool,
|
||||
ProgramManager programManager, TaskMonitor monitor) {
|
||||
public static void importSingleFile(PluginTool tool, ProgramManager programManager, FSRL fsrl,
|
||||
DomainFolder destFolder, LoadSpec loadSpec, String programName, List<Option> options,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
Objects.requireNonNull(monitor);
|
||||
|
||||
// Do a normal single-file import
|
||||
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
||||
|
||||
Object consumer = new Object();
|
||||
|
@ -392,7 +434,7 @@ public class ImporterUtilities {
|
|||
firstProgram ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE;
|
||||
programManager.openProgram(program, openState);
|
||||
}
|
||||
ImporterUtilities.setProgramProperties(program, fsrl, monitor);
|
||||
setProgramProperties(program, fsrl, monitor);
|
||||
ProgramMappingService.createAssociation(fsrl, program);
|
||||
importedFilesSet.add(program.getDomainFile());
|
||||
}
|
||||
|
@ -416,8 +458,11 @@ public class ImporterUtilities {
|
|||
}
|
||||
}
|
||||
|
||||
public static void doAddToProgram(FSRL fsrl, LoadSpec loadSpec, List<Option> options,
|
||||
Program program, TaskMonitor monitor, PluginTool tool) {
|
||||
public static void addContentToProgram(PluginTool tool, Program program, FSRL fsrl,
|
||||
LoadSpec loadSpec, List<Option> options, TaskMonitor monitor) {
|
||||
|
||||
Objects.requireNonNull(monitor);
|
||||
|
||||
MessageLog messageLog = new MessageLog();
|
||||
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
||||
loadSpec.getLoader().loadInto(bp, loadSpec, options, messageLog, program, monitor,
|
||||
|
|
|
@ -18,7 +18,6 @@ package ghidra.plugins.fsbrowser;
|
|||
import java.awt.Component;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
@ -40,6 +39,7 @@ import ghidra.app.util.opinion.LoaderService;
|
|||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.plugin.importer.ImporterUtilities;
|
||||
import ghidra.plugin.importer.ProgramMappingService;
|
||||
import ghidra.plugins.fsbrowser.tasks.GFileSystemExtractAllTask;
|
||||
|
@ -151,28 +151,17 @@ class FSBActionManager {
|
|||
if (!(contextObject instanceof FSBNode[])) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<FSRL> fsrls = new ArrayList<>();
|
||||
for (FSBNode node : (FSBNode[]) contextObject) {
|
||||
FSRL fsrl = node.getFSRL();
|
||||
if ((node instanceof FSBDirNode) || (node instanceof FSBRootNode)) {
|
||||
FSBRootNode rootNode = FSBUtils.getNodesRoot(node);
|
||||
GFileSystem fs = rootNode.getFSRef().getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider) {
|
||||
GFile gfile;
|
||||
try {
|
||||
gfile = fs.lookup(node.getFSRL().getPath());
|
||||
if (gfile != null &&
|
||||
((GFileSystemProgramProvider) fs).canProvideProgram(gfile)) {
|
||||
fsrls.add(fsrl);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore error and fall thru to normal file handling
|
||||
}
|
||||
}
|
||||
|
||||
FSRL validated = vaildateFsrl(fsrl, node);
|
||||
if (validated != null) {
|
||||
fsrls.add(validated);
|
||||
continue;
|
||||
}
|
||||
if (node instanceof FSBRootNode && fsrl.getFS().hasContainer()) {
|
||||
else if (node instanceof FSBRootNode && fsrl.getFS().hasContainer()) {
|
||||
// 'convert' a file system root node back into its container file
|
||||
fsrls.add(fsrl.getFS().getContainer());
|
||||
}
|
||||
|
@ -183,6 +172,28 @@ class FSBActionManager {
|
|||
return fsrls;
|
||||
}
|
||||
|
||||
private FSRL vaildateFsrl(FSRL fsrl, FSBNode node) {
|
||||
if ((node instanceof FSBDirNode) || (node instanceof FSBRootNode)) {
|
||||
FSBRootNode rootNode = FSBUtils.getNodesRoot(node);
|
||||
GFileSystem fs = rootNode.getFSRef().getFilesystem();
|
||||
if (fs instanceof GFileSystemProgramProvider) {
|
||||
GFile gfile;
|
||||
try {
|
||||
gfile = fs.lookup(node.getFSRL().getPath());
|
||||
if (gfile != null &&
|
||||
((GFileSystemProgramProvider) fs).canProvideProgram(gfile)) {
|
||||
return fsrl;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore error and return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private FSRL getLoadableFSRLFromContext(ActionContext context) {
|
||||
if (context == null || !(context.getContextObject() instanceof FSBNode)) {
|
||||
return null;
|
||||
|
@ -297,16 +308,17 @@ class FSBActionManager {
|
|||
|
||||
private void openProgramFromFile(FSRL file, String suggestedDestinationPath) {
|
||||
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
||||
if (pm != null) {
|
||||
AtomicBoolean success = new AtomicBoolean();
|
||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
||||
success.set(doOpenProgramFromFile(file, suggestedDestinationPath, pm, monitor));
|
||||
});
|
||||
if (!success.get()) {
|
||||
ImporterUtilities.showImportDialog(file, null, suggestedDestinationPath,
|
||||
plugin.getTool(), pm);
|
||||
}
|
||||
if (pm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
||||
boolean success = doOpenProgramFromFile(file, suggestedDestinationPath, pm, monitor);
|
||||
if (!success) {
|
||||
ImporterUtilities.showImportDialog(plugin.getTool(), pm, file, null,
|
||||
suggestedDestinationPath, monitor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean doOpenProgramFromFile(FSRL fsrl, String suggestedDestinationPath,
|
||||
|
@ -351,27 +363,27 @@ class FSBActionManager {
|
|||
* If there is no {@link ProgramManager} associated with the current tool, one will
|
||||
* be searched for and the user may be prompted for confirmation, or warned if
|
||||
* no PM found.
|
||||
* <p>
|
||||
* Package visible only.
|
||||
*
|
||||
* @param files List of {@link FSRL} files to open in the active {@link ProgramManager}.
|
||||
*/
|
||||
private void openProgramsFromFiles(List<FSRL> files) {
|
||||
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
||||
List<FSRL> unmatchedFiles = new ArrayList<>();
|
||||
if (pm != null) {
|
||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
||||
List<FSRL> tmpUnmatchedFiles = doOpenProgramsFromFiles(files, pm, monitor);
|
||||
unmatchedFiles.addAll(tmpUnmatchedFiles);
|
||||
});
|
||||
if (pm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
||||
List<FSRL> unmatchedFiles = doOpenProgramsFromFiles(files, pm, monitor);
|
||||
|
||||
if (unmatchedFiles.size() == 1) {
|
||||
ImporterUtilities.showImportDialog(unmatchedFiles.get(0), null, null,
|
||||
plugin.getTool(), pm);
|
||||
ImporterUtilities.showImportDialog(plugin.getTool(), pm, unmatchedFiles.get(0),
|
||||
null, null, monitor);
|
||||
}
|
||||
else if (unmatchedFiles.size() > 1) {
|
||||
BatchImportDialog.showAndImport(plugin.getTool(), null, unmatchedFiles, null, pm);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -420,7 +432,8 @@ class FSBActionManager {
|
|||
ProgramManager programManager, TaskMonitor monitor, int programsOpened) {
|
||||
boolean doSearch = isProjectSmallEnoughToSearchWithoutWarningUser() ||
|
||||
OptionDialog.showYesNoDialog(null, "Search Project for matching programs?",
|
||||
"Search entire Project for matching programs? (WARNING, could take large amount of time)") == OptionDialog.YES_OPTION;
|
||||
"Search entire Project for matching programs? " +
|
||||
"(WARNING, could take large amount of time)") == OptionDialog.YES_OPTION;
|
||||
|
||||
Map<FSRL, DomainFile> matchedFSRLs =
|
||||
doSearch ? ProgramMappingService.searchProjectForMatchingFiles(fsrlList, monitor)
|
||||
|
@ -1081,13 +1094,17 @@ class FSBActionManager {
|
|||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
FSRL fsrl = getLoadableFSRLFromContext(context);
|
||||
if (fsrl != null) {
|
||||
String treePath = getFormattedTreePath(context);
|
||||
String suggestedDestinationPath =
|
||||
FilenameUtils.getFullPathNoEndSeparator(treePath).replaceAll(":/", "/");
|
||||
ImporterUtilities.showImportDialog(fsrl, null, suggestedDestinationPath,
|
||||
plugin.getTool(), FSBUtils.getProgramManager(plugin.getTool(), false));
|
||||
if (fsrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String treePath = getFormattedTreePath(context);
|
||||
String suggestedPath =
|
||||
FilenameUtils.getFullPathNoEndSeparator(treePath).replaceAll(":/", "/");
|
||||
|
||||
PluginTool tool = plugin.getTool();
|
||||
ProgramManager pm = FSBUtils.getProgramManager(tool, false);
|
||||
ImporterUtilities.showImportDialog(tool, pm, fsrl, null, suggestedPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,365 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.util.task;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Tests for the {@link TaskMonitorService}
|
||||
*/
|
||||
public class TaskMonitorServiceTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that in a single-threaded environment, the {@link TaskMonitorService}
|
||||
* returns the correct instance of {@link TaskMonitor}. In this case, the initial
|
||||
* monitor is registered by the {@link TaskLauncher task launcher}; the next call
|
||||
* to {@link TaskMonitorService#getMonitor() getMonitor} should return the same.
|
||||
*/
|
||||
@Test
|
||||
public void testSingleThread() {
|
||||
|
||||
TaskLauncher.launch(new Task("task1") {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor newMonitor = TaskMonitorService.getMonitor();
|
||||
assertSame(newMonitor, monitor);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that in a single-threaded environment, the {@link TaskMonitorService}
|
||||
* returns the same instance of {@link TaskMonitor} each time one is
|
||||
* requested.
|
||||
* <p>
|
||||
* Note that the first time a monitor is requested it will always return the primary
|
||||
* monitor that allows progress changes. Each subsequent time it will return the
|
||||
* secondary monitor; these secondary monitors is what this test is verifying.
|
||||
*/
|
||||
@Test
|
||||
public void testSingleThreadSecondaryMonitors() {
|
||||
|
||||
TaskLauncher.launch(new Task("task1") {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
// First monitor requested is always the primary - just make the call so
|
||||
// we can get to retrieving some secondary monitors
|
||||
TaskMonitorService.getMonitor();
|
||||
|
||||
TaskMonitor secondaryMonitor1 = TaskMonitorService.getMonitor();
|
||||
TaskMonitor secondaryMonitor2 = TaskMonitorService.getMonitor();
|
||||
|
||||
assertEquals(secondaryMonitor1, secondaryMonitor2);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that in a multi-threaded environment, the {@link TaskMonitorService}
|
||||
* returns a different instance of {@link TaskMonitor} for each thread.
|
||||
*/
|
||||
@Test
|
||||
public void testMultipleThreads() {
|
||||
|
||||
AtomicReference<TaskMonitor> localMonitor1 = new AtomicReference<>();
|
||||
AtomicReference<TaskMonitor> localMonitor2 = new AtomicReference<>();
|
||||
|
||||
TaskLauncher.launch(new Task("task1") {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
localMonitor1.set(TaskMonitorService.getMonitor());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
TaskLauncher.launch(new Task("task2") {
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
localMonitor2.set(TaskMonitorService.getMonitor());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
|
||||
assertNotSame(localMonitor1.get(), localMonitor2.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that if we try to register a monitor while on the Swing thread an exception will be
|
||||
* thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testSetMonitorOnSwingThread() {
|
||||
|
||||
SystemUtilities.runSwingNow(() -> {
|
||||
|
||||
try {
|
||||
TaskMonitorService.register(TaskMonitor.DUMMY);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected catch
|
||||
return;
|
||||
}
|
||||
|
||||
fail("Successful register of monitor on Swing thread (should not have been allowed)");
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that if a client attempts to set a monitor on a thread that already has a monitor,
|
||||
* an exception will be thrown.
|
||||
* <p>
|
||||
* Note: The first monitor is registered behind the scenes by the task launcher
|
||||
*/
|
||||
@Test
|
||||
public void testRegisterMultipleMonitors() {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
try {
|
||||
TaskMonitorService.register(TaskMonitor.DUMMY);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// expected catch
|
||||
return;
|
||||
}
|
||||
|
||||
fail("Successful register of new monitor (should not have been allowed)");
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that unique monitor id's are correctly assigned to each thread that registers
|
||||
* a monitor
|
||||
*/
|
||||
@Test
|
||||
public void testMonitorIds() {
|
||||
|
||||
AtomicInteger monitor1Id = new AtomicInteger();
|
||||
AtomicInteger monitor2Id = new AtomicInteger();
|
||||
AtomicInteger monitor3Id = new AtomicInteger();
|
||||
|
||||
Thread thread1 =
|
||||
new Thread(() -> monitor1Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
|
||||
|
||||
Thread thread2 =
|
||||
new Thread(() -> monitor2Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
|
||||
|
||||
Thread thread3 =
|
||||
new Thread(() -> monitor3Id.set(TaskMonitorService.register(TaskMonitor.DUMMY)));
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
thread3.start();
|
||||
|
||||
waitForTasks();
|
||||
|
||||
boolean areIdsDifferent =
|
||||
(monitor1Id.get() != monitor2Id.get()) && (monitor2Id.get() != monitor3Id.get());
|
||||
assertTrue(areIdsDifferent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that if a client attempts to remove a monitor with an invalid id the request
|
||||
* will fail
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveMonitorFail() {
|
||||
|
||||
AtomicBoolean removed = new AtomicBoolean(true);
|
||||
|
||||
// All monitor id's are positive integers; this is guaranteed to generated a failure
|
||||
final int BOGUS_ID = -1;
|
||||
|
||||
Thread thread1 = new Thread(() -> {
|
||||
TaskMonitorService.register(TaskMonitor.DUMMY);
|
||||
try {
|
||||
TaskMonitorService.remove(BOGUS_ID);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected catch
|
||||
removed.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
thread1.start();
|
||||
|
||||
waitForTasks();
|
||||
|
||||
assertFalse(removed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a monitor can be successfully removed given a correct id
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveMonitorSuccess() {
|
||||
|
||||
AtomicBoolean removed = new AtomicBoolean(false);
|
||||
|
||||
AtomicInteger monitorId = new AtomicInteger();
|
||||
|
||||
Thread thread1 = new Thread(() -> {
|
||||
monitorId.set(TaskMonitorService.register(TaskMonitor.DUMMY));
|
||||
|
||||
try {
|
||||
TaskMonitorService.remove(monitorId.get());
|
||||
removed.set(true);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// should not be here
|
||||
fail();
|
||||
}
|
||||
});
|
||||
|
||||
thread1.start();
|
||||
|
||||
waitForTasks();
|
||||
|
||||
assertTrue(removed.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the first monitor returned from the service will be the
|
||||
* primary monitor
|
||||
*/
|
||||
@Test
|
||||
public void testRetrievePrimaryMonitor() {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof TaskDialog);
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that any subsequent requests after the initial request will result in
|
||||
* a {@link SecondaryTaskDialog} being returned
|
||||
*/
|
||||
@Test
|
||||
public void testRetrieveSecondaryMonitor() {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof TaskDialog);
|
||||
|
||||
monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof SecondaryTaskMonitor);
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that calling finish on a primary monitor will cause it to
|
||||
* be set back to an uninitialized state.
|
||||
*/
|
||||
@Test
|
||||
public void testMonitorFinishPrimary() {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof TaskDialog);
|
||||
|
||||
monitor1.finished();
|
||||
|
||||
monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof TaskDialog);
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that calling finish on a secondary monitor will NOT cause
|
||||
* it to be uninitialized (only primary monitors can reset this state)
|
||||
*/
|
||||
@Test
|
||||
public void testMonitorFinishSecondary() {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof TaskDialog);
|
||||
|
||||
monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof SecondaryTaskMonitor);
|
||||
|
||||
monitor1.finished();
|
||||
|
||||
monitor1 = TaskMonitorService.getMonitor();
|
||||
assertTrue(monitor1 instanceof SecondaryTaskMonitor);
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
}
|
|
@ -67,7 +67,6 @@ public class DialogComponentProvider
|
|||
protected JButton dismissButton;
|
||||
private boolean isAlerting;
|
||||
private JLabel statusLabel;
|
||||
private JLabel subStatusLabel;
|
||||
private JPanel statusProgPanel; // contains status panel and progress panel
|
||||
private Timer showTimer;
|
||||
private TaskScheduler taskScheduler;
|
||||
|
@ -609,11 +608,6 @@ public class DialogComponentProvider
|
|||
public void setStatusText(String text) {
|
||||
setStatusText(text, MessageType.INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubStatusText(String text) {
|
||||
setSubStatusText(text, MessageType.INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text in the dialog's status line using the specified message type to control
|
||||
|
@ -627,24 +621,12 @@ public class DialogComponentProvider
|
|||
setStatusText(message, type, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubStatusText(String message, MessageType type) {
|
||||
setSubStatusText(message, type, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusText(String message, MessageType type, boolean alert) {
|
||||
|
||||
String text = StringUtils.isBlank(message) ? " " : message;
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubStatusText(String message, MessageType type, boolean alert) {
|
||||
|
||||
String text = StringUtils.isBlank(message) ? " " : message;
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetSubStatusText(text, type, alert));
|
||||
}
|
||||
|
||||
private void doSetStatusText(String text, MessageType type, boolean alert) {
|
||||
|
||||
|
@ -659,14 +641,12 @@ public class DialogComponentProvider
|
|||
alertMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doSetSubStatusText(String text, MessageType type, boolean alert) {
|
||||
|
||||
SystemUtilities.assertThisIsTheSwingThread(
|
||||
"Setting text must be performed on the Swing thread");
|
||||
|
||||
subStatusLabel.setText(text);
|
||||
subStatusLabel.setForeground(getStatusColor(type));
|
||||
updateStatusToolTip();
|
||||
|
||||
if (alert) {
|
||||
|
@ -711,13 +691,11 @@ public class DialogComponentProvider
|
|||
// normal Swing mechanism may not have yet happened).
|
||||
mainPanel.validate();
|
||||
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
|
||||
subStatusLabel.setVisible(false);
|
||||
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
|
||||
animator.addTarget(new TimingTargetAdapter() {
|
||||
@Override
|
||||
public void end() {
|
||||
statusLabel.setVisible(true);
|
||||
subStatusLabel.setVisible(true);
|
||||
alertFinishedCallback.call();
|
||||
isAlerting = false;
|
||||
}
|
||||
|
@ -828,7 +806,6 @@ public class DialogComponentProvider
|
|||
public void clearStatusText() {
|
||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
||||
statusLabel.setText(" ");
|
||||
subStatusLabel.setText(" ");
|
||||
updateStatusToolTip();
|
||||
});
|
||||
}
|
||||
|
@ -842,15 +819,6 @@ public class DialogComponentProvider
|
|||
return statusLabel.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secondary status message
|
||||
*
|
||||
* @return the secondary status message
|
||||
*/
|
||||
public String getSubStatusText() {
|
||||
return subStatusLabel.getText();
|
||||
}
|
||||
|
||||
protected JLabel getStatusLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
|
@ -941,23 +909,13 @@ public class DialogComponentProvider
|
|||
updateStatusToolTip();
|
||||
}
|
||||
});
|
||||
|
||||
subStatusLabel = new JLabel(" ");
|
||||
subStatusLabel.setName("subStatusLabel");
|
||||
subStatusLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
subStatusLabel.setForeground(Color.blue);
|
||||
subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(Font.ITALIC));
|
||||
subStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 3, 5));
|
||||
subStatusLabel.setFont(subStatusLabel.getFont().deriveFont(9.0f));
|
||||
|
||||
// use a strut panel so the size of the message area does not change if we make
|
||||
// the message label not visible
|
||||
int height =
|
||||
statusLabel.getPreferredSize().height + subStatusLabel.getPreferredSize().height + 5;
|
||||
int height = statusLabel.getPreferredSize().height;
|
||||
|
||||
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
|
||||
panel.add(statusLabel, BorderLayout.CENTER);
|
||||
panel.add(subStatusLabel, BorderLayout.SOUTH);
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,180 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.util.task;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* {@link TaskMonitor} that restricts users from being able to update the progress bar. The class
|
||||
* is initialized with another, fully-featured monitor and forwards all requests to it,
|
||||
* but squashes calls to methods that are not allowed.
|
||||
* <p>
|
||||
* Note: Two instances of this class are deemed equal if they have the same {@link #parentMonitor},
|
||||
* hence the override of {@link #hashCode()} and {@link #equals(Object)}.
|
||||
*/
|
||||
public class SecondaryTaskMonitor implements TaskMonitor {
|
||||
|
||||
private TaskMonitor parentMonitor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param parentMonitor the fully-functional task monitor this is based off of
|
||||
*/
|
||||
public SecondaryTaskMonitor(TaskMonitor parentMonitor) {
|
||||
this.parentMonitor = parentMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to ensure that clients who have this type of monitor will only update the
|
||||
* secondary message when using this method
|
||||
*
|
||||
* @param message the message string to display
|
||||
*/
|
||||
@Override
|
||||
public void setMessage(String message) {
|
||||
if (parentMonitor instanceof TaskDialog) {
|
||||
((TaskDialog) parentMonitor).setSecondaryMessage(message);
|
||||
return;
|
||||
}
|
||||
parentMonitor.setMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelEnabled(boolean enable) {
|
||||
parentMonitor.setCancelEnabled(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInitialized(boolean init) {
|
||||
parentMonitor.setInitialized(init);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secondary monitors should not be able to reset progress or revert back
|
||||
* to an uninitialized state; hence the override.
|
||||
*/
|
||||
@Override
|
||||
public void finished() {
|
||||
synchronized (this) {
|
||||
setMessage("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelEnabled() {
|
||||
return parentMonitor.isCancelEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return parentMonitor.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cancel() {
|
||||
parentMonitor.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearCanceled() {
|
||||
parentMonitor.clearCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() throws CancelledException {
|
||||
parentMonitor.checkCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCancelledListener(CancelledListener listener) {
|
||||
parentMonitor.addCancelledListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCancelledListener(CancelledListener listener) {
|
||||
parentMonitor.removeCancelledListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaximum() {
|
||||
return parentMonitor.getMaximum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getProgress() {
|
||||
return parentMonitor.getProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowProgressValue(boolean showProgressValue) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProgress(long value) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(long max) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaximum(long max) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementProgress(long incrementAmount) {
|
||||
// squash
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((parentMonitor == null) ? 0 : parentMonitor.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SecondaryTaskMonitor other = (SecondaryTaskMonitor) obj;
|
||||
if (!Objects.equals(parentMonitor, other.parentMonitor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -42,6 +42,17 @@ import util.CollectionUtils;
|
|||
* .setStatusTextAlignment(SwingConstants.LEADING)
|
||||
* .launchModal();
|
||||
* </pre>
|
||||
*
|
||||
* Or,
|
||||
*
|
||||
* <pre>
|
||||
* TaskBuilder.withRunnable(monitor -> doWork(parameter, monitor))
|
||||
* .setTitle("Task Title")
|
||||
* .setHasProgress(true)
|
||||
* .setCanCancel(true)
|
||||
* .setStatusTextAlignment(SwingConstants.LEADING)
|
||||
* .launchModal();
|
||||
* </pre>
|
||||
*/
|
||||
public class TaskBuilder {
|
||||
|
||||
|
@ -57,7 +68,25 @@ public class TaskBuilder {
|
|||
private int statusTextAlignment = SwingConstants.CENTER;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* A convenience method to start a builder using the given runnable. After calling this
|
||||
* method you are still required to call {@link #setTitle(String)}.
|
||||
*
|
||||
* <p>This method allows for a more attractive fluent API usage than does the constructor
|
||||
* (see the javadoc header).
|
||||
*
|
||||
* @param r the runnable
|
||||
* @return this builder
|
||||
*/
|
||||
public static TaskBuilder withRunnable(MonitoredRunnable r) {
|
||||
return new TaskBuilder(r);
|
||||
}
|
||||
|
||||
private TaskBuilder(MonitoredRunnable r) {
|
||||
this.runnable = Objects.requireNonNull(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param title the required title for your task. This will appear as the title of the
|
||||
* task dialog
|
||||
|
@ -68,6 +97,18 @@ public class TaskBuilder {
|
|||
this.runnable = Objects.requireNonNull(runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this task. The title must be set before calling any of the
|
||||
* <code>launch</code> methods.
|
||||
*
|
||||
* @param title the title
|
||||
* @return this builder
|
||||
*/
|
||||
public TaskBuilder setTitle(String title) {
|
||||
this.title = Objects.requireNonNull(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this task reports progress. The default is <tt>true</tt>.
|
||||
*
|
||||
|
@ -149,6 +190,8 @@ public class TaskBuilder {
|
|||
* Launches the task built by this builder, using a blocking modal dialog.
|
||||
*/
|
||||
public void launchModal() {
|
||||
validate();
|
||||
|
||||
boolean isModal = true;
|
||||
Task t = new TaskBuilderTask(isModal);
|
||||
int delay = getDelay(launchDelay, isModal);
|
||||
|
@ -160,6 +203,8 @@ public class TaskBuilder {
|
|||
* @return the launcher that launched the task
|
||||
*/
|
||||
public TaskLauncher launchNonModal() {
|
||||
validate();
|
||||
|
||||
boolean isModal = false;
|
||||
Task t = new TaskBuilderTask(isModal);
|
||||
int delay = getDelay(launchDelay, isModal);
|
||||
|
@ -167,6 +212,12 @@ public class TaskBuilder {
|
|||
return launcher;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (title == null) {
|
||||
throw new NullPointerException("Task title cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
private static int getDelay(int userDelay, boolean isModal) {
|
||||
if (userDelay >= 0) {
|
||||
return userDelay;
|
||||
|
|
|
@ -17,7 +17,6 @@ package ghidra.util.task;
|
|||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -38,10 +37,10 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
|
||||
/** Timer used to give the task a chance to complete */
|
||||
private static final int SLEEPY_TIME = 10;
|
||||
|
||||
|
||||
/** Amount of time to wait before showing the monitor dialog */
|
||||
private final static int MAX_DELAY = 200000;
|
||||
|
||||
|
||||
public final static int DEFAULT_WIDTH = 275;
|
||||
|
||||
private Timer showTimer;
|
||||
|
@ -57,24 +56,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
/** Runnable that updates the primary message label in the dialog */
|
||||
private Runnable updatePrimaryMessageRunnable;
|
||||
|
||||
/** Runnable that updates the secondary message label in the dialog */
|
||||
private Runnable updateSecondaryMessageRunnable;
|
||||
|
||||
/** If not null, then the value of the string has yet to be rendered */
|
||||
private String newPrimaryMessage;
|
||||
|
||||
/** If not null, then the value of the string has yet to be rendered */
|
||||
private String newSecondaryMessage;
|
||||
|
||||
/**
|
||||
* Indicates if this monitor has been initialized for progress updates. If this value
|
||||
* is set to true, the {@link TaskMonitorService} will not return the monitor to
|
||||
* another caller (only one client should be able to update progress at a time).
|
||||
*/
|
||||
private AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
private SecondaryTaskMonitor secondaryTaskMonitor;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -108,7 +92,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) {
|
||||
this(null, title, isModal, canCancel, hasProgress);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
|
@ -126,20 +110,10 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
setup(canCancel, hasProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return initialized.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInitialized(boolean init) {
|
||||
this.initialized.set(init);
|
||||
}
|
||||
|
||||
private void setup(boolean canCancel, boolean hasProgress) {
|
||||
monitorComponent = new TaskMonitorComponent(false, false);
|
||||
chompingBitsPanel = new ChompingBitsAnimationPanel();
|
||||
|
||||
|
||||
setCancelEnabled(canCancel);
|
||||
setRememberLocation(false);
|
||||
setRememberSize(false);
|
||||
|
@ -154,12 +128,6 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
newPrimaryMessage = null;
|
||||
}
|
||||
};
|
||||
updateSecondaryMessageRunnable = () -> {
|
||||
setSubStatusText(newSecondaryMessage);
|
||||
synchronized (TaskDialog.this) {
|
||||
newSecondaryMessage = null;
|
||||
}
|
||||
};
|
||||
shouldCancelRunnable = () -> {
|
||||
int currentTaskID = taskID.get();
|
||||
|
||||
|
@ -171,7 +139,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
|
||||
mainPanel = new JPanel(new BorderLayout());
|
||||
addWorkPanel(mainPanel);
|
||||
|
||||
|
||||
if (hasProgress) {
|
||||
installProgressMonitor();
|
||||
}
|
||||
|
@ -306,19 +274,6 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the secondary message on the task monitor
|
||||
*
|
||||
* @param str the string to update
|
||||
*/
|
||||
synchronized public void setSecondaryMessage(String str) {
|
||||
boolean invoke = (newSecondaryMessage == null);
|
||||
if (invoke) {
|
||||
newSecondaryMessage = str;
|
||||
SwingUtilities.invokeLater(updateSecondaryMessageRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelEnabled(boolean enable) {
|
||||
monitorComponent.setCancelEnabled(enable);
|
||||
|
@ -386,7 +341,6 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
// only to show a progress dialog if enough time has elapsed.
|
||||
//
|
||||
GTimer.scheduleRunnable(delay, () -> {
|
||||
|
||||
if (isCompleted()) {
|
||||
return;
|
||||
}
|
||||
|
@ -467,12 +421,4 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
|||
public void removeCancelledListener(CancelledListener listener) {
|
||||
monitorComponent.removeCancelledListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized TaskMonitor getSecondaryMonitor() {
|
||||
if (secondaryTaskMonitor == null) {
|
||||
secondaryTaskMonitor = new SecondaryTaskMonitor(this);
|
||||
}
|
||||
return secondaryTaskMonitor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ import java.awt.Component;
|
|||
import javax.swing.SwingUtilities;
|
||||
|
||||
import generic.util.WindowUtilities;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.TaskUtilities;
|
||||
|
||||
/**
|
||||
* Class to initiate a Task in a new Thread, and to show a progress dialog that indicates
|
||||
|
@ -285,16 +286,8 @@ public class TaskLauncher {
|
|||
|
||||
private TaskDialog buildTaskDialog(Component comp, int dialogWidth) {
|
||||
|
||||
//
|
||||
// This class may be used by background threads. Make sure that our GUI creation is
|
||||
// on the Swing thread to prevent exceptions while painting (as seen when using the
|
||||
// Nimbus Look and Feel).
|
||||
//
|
||||
|
||||
SystemUtilities.runSwingNow(() -> {
|
||||
taskDialog = createTaskDialog(comp);
|
||||
taskDialog.setMinimumSize(dialogWidth, 0);
|
||||
});
|
||||
taskDialog = createTaskDialog(comp);
|
||||
taskDialog.setMinimumSize(dialogWidth, 0);
|
||||
|
||||
if (task.isInterruptible() || task.isForgettable()) {
|
||||
taskDialog.addCancelledListener(monitorChangeListener);
|
||||
|
|
|
@ -23,12 +23,10 @@ import java.util.concurrent.*;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class TaskDialogTest extends AbstractDockingTest {
|
||||
|
||||
|
@ -95,105 +93,53 @@ public class TaskDialogTest extends AbstractDockingTest {
|
|||
assertTrue(dialogSpy.wasShown());
|
||||
assertSwingThreadFinishedBeforeTask();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verifies that if the dialog cancel button is activated, the task is cancelled
|
||||
*/
|
||||
@Test
|
||||
public void testTaskCancel() throws Exception {
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
|
||||
|
||||
dialogSpy.doShow();
|
||||
|
||||
|
||||
waitForTask();
|
||||
|
||||
|
||||
assertFalse(dialogSpy.isCancelled());
|
||||
dialogSpy.cancel();
|
||||
assertTrue(dialogSpy.isCancelled());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Verifies that if the task does not allow cancellation, the cancel button on the GUI
|
||||
* is disabled
|
||||
*/
|
||||
@Test
|
||||
public void testTaskNoCancel() throws Exception {
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
SlowModalTask task = new SlowModalTask();
|
||||
TaskDialogSpy dialogSpy = launchTask(task);
|
||||
|
||||
|
||||
dialogSpy.doShow();
|
||||
dialogSpy.setCancelEnabled(false);
|
||||
|
||||
|
||||
waitForTask();
|
||||
|
||||
|
||||
assertFalse(dialogSpy.isCancelEnabled());
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the progress value can be successfully updated
|
||||
* after using the {@link TaskMonitorService} to retrieve a monitor.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdateProgressSuccess() throws Exception {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
long val = monitor1.getProgress();
|
||||
|
||||
monitor1.setProgress(10);
|
||||
val = monitor1.getProgress();
|
||||
assertEquals(val, 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Verifies that the progress value will NOT be updated if the caller is a
|
||||
* secondary monitor. As a bonus, this also verifies that the Task Launcher does
|
||||
* not lock the task for future progress updates when a new task is launched.
|
||||
*/
|
||||
@Test
|
||||
public void testUpdatePogressFail() throws Exception {
|
||||
|
||||
TaskLauncher.launch(new Task("task") {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
TaskMonitor monitor1 = TaskMonitorService.getMonitor();
|
||||
TaskMonitor monitor2 = TaskMonitorService.getMonitor();
|
||||
|
||||
// Update should be accepted
|
||||
monitor1.setProgress(10);
|
||||
|
||||
// Update should fail
|
||||
monitor2.setProgress(20);
|
||||
|
||||
long val = monitor2.getProgress();
|
||||
assertEquals(val, 10);
|
||||
}
|
||||
});
|
||||
|
||||
waitForTasks();
|
||||
}
|
||||
|
||||
private void assertSwingThreadBlockedForTask() {
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (!swingIsLast) {
|
||||
System.out.println("Events " + eventQueue);
|
||||
fail("The Swing thread did not block until the task finished");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSwingThreadFinishedBeforeTask() {
|
||||
int size = eventQueue.size();
|
||||
TDEvent lastEvent = eventQueue.peekLast();
|
||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||
if (swingIsLast) {
|
||||
System.out.println("Events (" + size + ")\n\t" + StringUtils.join(eventQueue, "\n\t"));
|
||||
fail("The Swing thread blocked until the task finished");
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +190,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
|||
public TaskDialogSpy(Task task) {
|
||||
super(task);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doShow() {
|
||||
shown.set(true);
|
||||
|
|
|
@ -41,35 +41,10 @@ public interface StatusListener {
|
|||
* @param alert true to grab the user's attention
|
||||
*/
|
||||
void setStatusText(String text, MessageType type, boolean alert);
|
||||
|
||||
/**
|
||||
* Sets the subtask text
|
||||
*
|
||||
* @param text the text to set
|
||||
*/
|
||||
void setSubStatusText(String text);
|
||||
|
||||
/**
|
||||
* Sets the subtask text
|
||||
*
|
||||
* @param text the text to set
|
||||
* @param type the message type
|
||||
*/
|
||||
void setSubStatusText(String text, MessageType type);
|
||||
|
||||
/**
|
||||
* Sets the subtask text
|
||||
*
|
||||
* @param text the text to set
|
||||
* @param type the message type
|
||||
* @param alert if true, an alert will be generated
|
||||
*/
|
||||
void setSubStatusText(String text, MessageType type, boolean alert);
|
||||
|
||||
/**
|
||||
* Clear the current status - same as setStatusText("")
|
||||
* without being recorded
|
||||
*/
|
||||
void clearStatusText();
|
||||
|
||||
}
|
||||
|
|
|
@ -24,9 +24,7 @@ import ghidra.util.*;
|
|||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Base class for Tasks to be run in separate threads.
|
||||
*
|
||||
*
|
||||
* Base class for Tasks to be run in separate threads
|
||||
*/
|
||||
public abstract class Task implements MonitoredRunnable {
|
||||
private String title;
|
||||
|
@ -37,7 +35,7 @@ public abstract class Task implements MonitoredRunnable {
|
|||
private boolean isForgettable;
|
||||
protected boolean waitForTaskCompleted = false;
|
||||
private Set<TaskListener> listeners = new HashSet<>();
|
||||
private TaskMonitor taskMonitor;
|
||||
protected TaskMonitor taskMonitor = TaskMonitor.DUMMY;
|
||||
|
||||
/**
|
||||
* Creates new Task.
|
||||
|
@ -70,9 +68,9 @@ public abstract class Task implements MonitoredRunnable {
|
|||
* progress indicator
|
||||
* @param isModal true means that the dialog is modal and the task has to
|
||||
* complete or be canceled before any other action can occur
|
||||
* @param waitForTaskCompleted true causes the running thread to block until the finish or cancelled
|
||||
* callback has completed on the swing thread. Note: passing true only makes sense if the
|
||||
* task is modal.
|
||||
* @param waitForTaskCompleted true causes the running thread to block until the finish or
|
||||
* cancelled callback has completed on the swing thread. Note: passing true
|
||||
* only makes sense if the task is modal.
|
||||
*/
|
||||
public Task(String title, boolean canCancel, boolean hasProgress, boolean isModal,
|
||||
boolean waitForTaskCompleted) {
|
||||
|
@ -113,9 +111,6 @@ public abstract class Task implements MonitoredRunnable {
|
|||
* When an object implementing interface <code>Runnable</code> is used to create a thread,
|
||||
* starting the thread causes the object's <code>run</code> method to be called in that
|
||||
* separately executing thread.
|
||||
* <p>
|
||||
* Note that the monitor is handed to the {@link TaskMonitorService} here so it will be
|
||||
* available to any users request it via {@link TaskMonitorService#getMonitor() getMonitor}.
|
||||
*
|
||||
* @param monitor the task monitor
|
||||
*/
|
||||
|
@ -123,8 +118,6 @@ public abstract class Task implements MonitoredRunnable {
|
|||
public final void monitoredRun(TaskMonitor monitor) {
|
||||
this.taskMonitor = monitor;
|
||||
|
||||
int monitorId = TaskMonitorService.register(monitor);
|
||||
|
||||
// this will be removed from SystemUtilities in Task.run() after the task is finished
|
||||
TaskUtilities.addTrackedTask(this, monitor);
|
||||
|
||||
|
@ -141,14 +134,8 @@ public abstract class Task implements MonitoredRunnable {
|
|||
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
|
||||
}
|
||||
finally {
|
||||
|
||||
// This should not be necessary since the thread local object will be cleaned up
|
||||
// by the GC when the thread terminates, but it's here in case we ever use this
|
||||
// in conjunction with thread pools and have to manually remove them.
|
||||
TaskMonitorService.remove(monitorId);
|
||||
|
||||
TaskUtilities.removeTrackedTask(this);
|
||||
this.taskMonitor = null;
|
||||
this.taskMonitor = null;
|
||||
}
|
||||
|
||||
notifyTaskListeners(isCancelled);
|
||||
|
|
|
@ -27,8 +27,7 @@ import generic.jar.ResourceFile;
|
|||
import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorService;
|
||||
import ghidra.util.task.TaskBuilder;
|
||||
|
||||
/**
|
||||
* Default Language service used gather up all the languages that were found
|
||||
|
@ -86,18 +85,21 @@ public class DefaultLanguageService implements LanguageService, ChangeListener {
|
|||
}
|
||||
|
||||
private void searchForProviders() {
|
||||
TaskMonitor monitor = TaskMonitorService.getMonitor();
|
||||
monitor.setMessage("Searching for language providers");
|
||||
try {
|
||||
Set<LanguageProvider> languageProviders =
|
||||
ClassSearcher.getInstances(LanguageProvider.class);
|
||||
Set<LanguageProvider> languageProviders =
|
||||
ClassSearcher.getInstances(LanguageProvider.class);
|
||||
|
||||
searchCompleted = true;
|
||||
processProviders(languageProviders);
|
||||
}
|
||||
finally {
|
||||
monitor.finished();
|
||||
}
|
||||
searchCompleted = true;
|
||||
|
||||
//@formatter:off
|
||||
TaskBuilder.withRunnable(monitor -> {
|
||||
processProviders(languageProviders); // load and cache
|
||||
})
|
||||
.setTitle("Language Search")
|
||||
.setCanCancel(false)
|
||||
.setHasProgress(false)
|
||||
.launchModal()
|
||||
;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,22 +107,23 @@ public class DefaultLanguageService implements LanguageService, ChangeListener {
|
|||
*/
|
||||
@Override
|
||||
public Language getLanguage(LanguageID languageID) throws LanguageNotFoundException {
|
||||
|
||||
TaskMonitor monitor = TaskMonitorService.getMonitor();
|
||||
monitor.setMessage("Retrieving language: " + languageID);
|
||||
try {
|
||||
LanguageInfo info = languageMap.get(languageID);
|
||||
|
||||
if (info == null) {
|
||||
throw new LanguageNotFoundException(languageID);
|
||||
}
|
||||
|
||||
Language lang = info.getLanguage();
|
||||
return lang;
|
||||
}
|
||||
finally {
|
||||
monitor.finished();
|
||||
LanguageInfo info = languageMap.get(languageID);
|
||||
if (info == null) {
|
||||
throw new LanguageNotFoundException(languageID);
|
||||
}
|
||||
|
||||
//@formatter:off
|
||||
TaskBuilder.withRunnable(monitor -> {
|
||||
info.getLanguage(); // load and cache
|
||||
})
|
||||
.setTitle("Loading language '" + languageID + "'")
|
||||
.setCanCancel(false)
|
||||
.setHasProgress(false)
|
||||
.launchModal()
|
||||
;
|
||||
//@formatter:on
|
||||
|
||||
return info.getLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,40 +42,6 @@ public interface TaskMonitor {
|
|||
*/
|
||||
public boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Returns true if the monitor has been initialized
|
||||
*
|
||||
* @return true if the monitor has been initialized
|
||||
*/
|
||||
public default boolean isInitialized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initialization state of the monitor
|
||||
*
|
||||
* @param init true for initialized, false otherwise
|
||||
*/
|
||||
public default void setInitialized(boolean init) {
|
||||
// do nothing - this is defaulted for backward compatibility so current
|
||||
// task monitor implementations do not have to change
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the monitor to an uninitialized state. This will result in the primary
|
||||
* monitor being returned from the {@link TaskMonitorService} on the next
|
||||
* invocation.
|
||||
*/
|
||||
public default void finished() {
|
||||
synchronized (this) {
|
||||
setMessage("");
|
||||
setProgress(0);
|
||||
setMaximum(0);
|
||||
setInitialized(false);
|
||||
clearCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True (the default) signals to paint the progress information inside of the progress bar
|
||||
*
|
||||
|
@ -89,16 +55,6 @@ public interface TaskMonitor {
|
|||
* @param message the message to display
|
||||
*/
|
||||
public void setMessage(String message);
|
||||
|
||||
/**
|
||||
* Returns a version of this monitor that cannot have its progress state changed. This is
|
||||
* meant for sub-tasks that should not be allowed to hijack task progress.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public default TaskMonitor getSecondaryMonitor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current progress value
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.util.task;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* Provides access to the {@link TaskMonitor} instance for the current thread. The first
|
||||
* time a monitor is requested via {@link #getMonitor()}, a "primary" monitor (one
|
||||
* that allows updating of task progress and status messages) is returned; all
|
||||
* subsequent requests will return a "secondary" monitor, which only allows
|
||||
* status message updates. This is to keep the progress bar from being updated
|
||||
* simultaneously by multiple parties.
|
||||
* <p>
|
||||
* Note: {@link TaskMonitor monitor} instances are registered with this service via the
|
||||
* {@link #register(TaskMonitor) setMonitor} call, and will be available to that thread until
|
||||
* the {@link #remove(int) remove} method is called.
|
||||
* <p>
|
||||
* Note: Because monitor instances are managed by a {@link ThreadLocal} object, they will be
|
||||
* cleaned up automatically by the GC when the thread is terminated.
|
||||
*/
|
||||
public class TaskMonitorService {
|
||||
|
||||
/**
|
||||
* The {@link TaskMonitor} instance. ThreadLocal ensures that each thread has access
|
||||
* to its own monitor.
|
||||
*/
|
||||
private static ThreadLocal<TaskMonitor> localMonitor = new ThreadLocal<TaskMonitor>() {
|
||||
|
||||
/**
|
||||
* Force the initial value to be null so users will have to call
|
||||
* {@link TaskMonitorService#register(TaskMonitor) register} to assign one
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
@Override
|
||||
protected TaskMonitor initialValue() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unique id for each thread monitor that is assigned when a monitor is
|
||||
* {@link #register(TaskMonitor) registered}. This is to ensure that only clients who have
|
||||
* a valid id can remove a monitor.
|
||||
*/
|
||||
private static ThreadLocal<Integer> localMonitorId = new ThreadLocal<Integer>() {
|
||||
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return nextId.getAndIncrement();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the next unique id for the monitor; this is updated each time a new monitor
|
||||
* is registered with the service.
|
||||
*/
|
||||
private static final AtomicInteger nextId = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* Returns the task monitor for the current thread. If one has not yet been registered,
|
||||
* a {@link StubTaskMonitor stub monitor} is returned.
|
||||
*
|
||||
* @return the task monitor
|
||||
*/
|
||||
public synchronized static TaskMonitor getMonitor() {
|
||||
|
||||
if (localMonitor.get() == null) {
|
||||
|
||||
// If no monitor is available, just return a stub. The alternative is to throw an
|
||||
// exception but this isn't considered an error condition in all cases.
|
||||
localMonitor.set(new StubTaskMonitor());
|
||||
}
|
||||
|
||||
// If the monitor has already been initialized, return the secondary monitor to prevent
|
||||
// the caller from hijacking the progress bar
|
||||
if (localMonitor.get().isInitialized()) {
|
||||
return localMonitor.get().getSecondaryMonitor();
|
||||
}
|
||||
|
||||
// This ensures that the next time this method is called, the service
|
||||
// will return the secondary monitor
|
||||
localMonitor.get().setInitialized(true);
|
||||
|
||||
return localMonitor.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given monitor for this thread
|
||||
*
|
||||
* @param monitor the task monitor to register
|
||||
* @return the unique id for the monitor
|
||||
*/
|
||||
public static int register(TaskMonitor monitor) {
|
||||
|
||||
// Don't allow callers to register a monitor if on the swing thread
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
throw new IllegalArgumentException("Attempting to set a monitor in the Swing thread!");
|
||||
}
|
||||
|
||||
// Don't allow users to register a monitor if there is already one registered for this
|
||||
// thread
|
||||
if (localMonitor.get() != null) {
|
||||
throw new IllegalArgumentException("Task monitor already assigned to this thread");
|
||||
}
|
||||
|
||||
localMonitor.set(monitor);
|
||||
|
||||
return localMonitorId.get();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the monitor from the thread local object. To protect against clients cavalierly
|
||||
* removing monitors, a valid monitor id must be provided; this is generated at the time
|
||||
* of monitor {@link #register(TaskMonitor) registration}.
|
||||
* <p>
|
||||
* Note: This should generally not need to be called as the GC will clean up thread local
|
||||
* objects when the associated thread is finished.
|
||||
*
|
||||
* @param monitorId the unique ID for the monitor to be removed
|
||||
*/
|
||||
public static void remove(int monitorId) {
|
||||
|
||||
if (monitorId != localMonitorId.get()) {
|
||||
throw new IllegalArgumentException("Invalid monitor id for this thread: " + monitorId);
|
||||
}
|
||||
|
||||
localMonitor.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the constructor - this should not be instantiated
|
||||
*/
|
||||
private TaskMonitorService() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue