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();
|
List<ResourceFile> files = searchAndFindAllOpinionXMLs();
|
||||||
for (ResourceFile file : files) {
|
for (ResourceFile file : files) {
|
||||||
Msg.debug(QueryOpinionService.class, "parsing " + file);
|
|
||||||
try {
|
try {
|
||||||
parseFile(file);
|
parseFile(file);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,14 @@ import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.VersionException;
|
import ghidra.util.exception.VersionException;
|
||||||
import ghidra.util.task.*;
|
import ghidra.util.task.Task;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class OpenProgramTask extends Task {
|
public class OpenProgramTask extends Task {
|
||||||
|
|
||||||
private final List<DomainFileInfo> domainFileInfoList = new ArrayList<>();
|
private final List<DomainFileInfo> domainFileInfoList = new ArrayList<>();
|
||||||
private List<Program> programList = new ArrayList<>();
|
private List<Program> programList = new ArrayList<>();
|
||||||
private TaskMonitor monitor;
|
|
||||||
private final Object consumer;
|
private final Object consumer;
|
||||||
private boolean silent; // if true operation does not permit interaction
|
private boolean silent; // if true operation does not permit interaction
|
||||||
private boolean noCheckout; // if true operation should not perform optional checkout
|
private boolean noCheckout; // if true operation should not perform optional checkout
|
||||||
|
@ -122,20 +123,17 @@ public class OpenProgramTask extends Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(TaskMonitor taskMonitor) {
|
public void run(TaskMonitor monitor) {
|
||||||
this.monitor = TaskMonitorService.getMonitor();
|
|
||||||
|
|
||||||
if (domainFileInfoList.size() > 1) {
|
taskMonitor.initialize(domainFileInfoList.size());
|
||||||
monitor.initialize(domainFileInfoList.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (DomainFileInfo domainFileInfo : domainFileInfoList) {
|
for (DomainFileInfo domainFileInfo : domainFileInfoList) {
|
||||||
if (monitor.isCancelled()) {
|
if (taskMonitor.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
openDomainFile(domainFileInfo);
|
openDomainFile(domainFileInfo);
|
||||||
|
|
||||||
monitor.incrementProgress(1);
|
taskMonitor.incrementProgress(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,12 +152,12 @@ public class OpenProgramTask extends Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openReadOnlyFile(DomainFile domainFile, int version) {
|
private void openReadOnlyFile(DomainFile domainFile, int version) {
|
||||||
monitor.setMessage("Opening " + domainFile.getName());
|
taskMonitor.setMessage("Opening " + domainFile.getName());
|
||||||
openReadOnly(domainFile, version);
|
openReadOnly(domainFile, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openVersionedFile(DomainFile domainFile, int 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);
|
openReadOnly(domainFile, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +166,7 @@ public class OpenProgramTask extends Task {
|
||||||
try {
|
try {
|
||||||
contentType = domainFile.getContentType();
|
contentType = domainFile.getContentType();
|
||||||
Program program =
|
Program program =
|
||||||
(Program) domainFile.getReadOnlyDomainObject(consumer, version, monitor);
|
(Program) domainFile.getReadOnlyDomainObject(consumer, version, taskMonitor);
|
||||||
|
|
||||||
if (program == null) {
|
if (program == null) {
|
||||||
String errorMessage = "Can't open program - \"" + domainFile.getPathname() + "\"";
|
String errorMessage = "Can't open program - \"" + domainFile.getPathname() + "\"";
|
||||||
|
@ -203,7 +201,7 @@ public class OpenProgramTask extends Task {
|
||||||
|
|
||||||
private void openUnversionedFile(DomainFile domainFile) {
|
private void openUnversionedFile(DomainFile domainFile) {
|
||||||
String filename = domainFile.getName();
|
String filename = domainFile.getName();
|
||||||
monitor.setMessage("Opening " + filename);
|
taskMonitor.setMessage("Opening " + filename);
|
||||||
performOptionalCheckout(domainFile);
|
performOptionalCheckout(domainFile);
|
||||||
try {
|
try {
|
||||||
openFileMaybeUgrade(domainFile);
|
openFileMaybeUgrade(domainFile);
|
||||||
|
@ -241,7 +239,7 @@ public class OpenProgramTask extends Task {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Program program =
|
Program program =
|
||||||
(Program) domainFile.getDomainObject(consumer, false, recoverFile, monitor);
|
(Program) domainFile.getDomainObject(consumer, false, recoverFile, taskMonitor);
|
||||||
|
|
||||||
if (program != null) {
|
if (program != null) {
|
||||||
programList.add(program);
|
programList.add(program);
|
||||||
|
@ -251,7 +249,7 @@ public class OpenProgramTask extends Task {
|
||||||
catch (VersionException e) {
|
catch (VersionException e) {
|
||||||
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, openPromptText, e)) {
|
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, openPromptText, e)) {
|
||||||
Program program =
|
Program program =
|
||||||
(Program) domainFile.getDomainObject(consumer, true, recoverFile, monitor);
|
(Program) domainFile.getDomainObject(consumer, true, recoverFile, taskMonitor);
|
||||||
if (program != null) {
|
if (program != null) {
|
||||||
programList.add(program);
|
programList.add(program);
|
||||||
}
|
}
|
||||||
|
@ -284,8 +282,8 @@ public class OpenProgramTask extends Task {
|
||||||
CheckoutDialog dialog = new CheckoutDialog(domainFile, user);
|
CheckoutDialog dialog = new CheckoutDialog(domainFile, user);
|
||||||
if (dialog.showDialog() == CheckoutDialog.CHECKOUT) {
|
if (dialog.showDialog() == CheckoutDialog.CHECKOUT) {
|
||||||
try {
|
try {
|
||||||
monitor.setMessage("Checking Out " + domainFile.getName());
|
taskMonitor.setMessage("Checking Out " + domainFile.getName());
|
||||||
if (domainFile.checkout(dialog.exclusiveCheckout(), monitor)) {
|
if (domainFile.checkout(dialog.exclusiveCheckout(), taskMonitor)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Msg.showError(this, null, "Checkout Failed", "Exclusive checkout failed for: " +
|
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);
|
options = selectedLoader.getDefaultOptions(byteProvider, selectedLoadSpec, null, true);
|
||||||
}
|
}
|
||||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
TaskLauncher.launchNonModal("Import File", monitor -> {
|
||||||
ImporterUtilities.doAddToProgram(fsrl, selectedLoadSpec, options, addToProgram, monitor,
|
ImporterUtilities.addContentToProgram(tool, addToProgram, fsrl, selectedLoadSpec, options,
|
||||||
tool);
|
monitor);
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -348,8 +348,8 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||||
String programName = FilenameUtils.getName(programPath);
|
String programName = FilenameUtils.getName(programPath);
|
||||||
List<Option> localOptions = getOptions(loadSpec);
|
List<Option> localOptions = getOptions(loadSpec);
|
||||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
TaskLauncher.launchNonModal("Import File", monitor -> {
|
||||||
ImporterUtilities.doSingleImport(fsrl, importFolder, loadSpec, programName,
|
ImporterUtilities.importSingleFile(tool, programManager, fsrl, importFolder,
|
||||||
localOptions, tool, programManager, monitor);
|
loadSpec, programName, localOptions, monitor);
|
||||||
});
|
});
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,9 +67,9 @@ public class ImporterPlugin extends Plugin
|
||||||
implements FileImporterService, FrontEndable, ProjectListener {
|
implements FileImporterService, FrontEndable, ProjectListener {
|
||||||
|
|
||||||
private static final String IMPORT_MENU_GROUP = "Import";
|
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 =
|
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 importAction;
|
||||||
private DockingAction importSelectionAction;// NA in front-end
|
private DockingAction importSelectionAction;// NA in front-end
|
||||||
|
@ -165,9 +165,10 @@ public class ImporterPlugin extends Plugin
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importFile(DomainFolder folder, File file) {
|
public void importFile(DomainFolder folder, File file) {
|
||||||
|
|
||||||
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
||||||
ImporterUtilities.showImportDialog(fsrl, folder, null, getTool(),
|
ProgramManager manager = tool.getService(ProgramManager.class);
|
||||||
getTool().getService(ProgramManager.class));
|
ImporterUtilities.showImportDialog(tool, manager, fsrl, folder, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -362,55 +363,50 @@ public class ImporterPlugin extends Plugin
|
||||||
Program program = manager.getCurrentProgram();
|
Program program = manager.getCurrentProgram();
|
||||||
|
|
||||||
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
|
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);
|
ImporterUtilities.showAddToProgramDialog(fsrl, program, tool, monitor);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Import a selection of bytes from the listing window.
|
|
||||||
*/
|
|
||||||
protected void doImportSelectionAction(ProgramSelection selection) {
|
protected void doImportSelectionAction(ProgramSelection selection) {
|
||||||
if (selection != null && selection.getNumAddressRanges() == 1) {
|
if (selection == null || selection.getNumAddressRanges() != 1) {
|
||||||
AddressRange range = selection.getFirstRange();// should only be 1
|
return;
|
||||||
if (range.getLength() < (Integer.MAX_VALUE & 0xffffffffL)) {
|
}
|
||||||
ProgramManager programManager = tool.getService(ProgramManager.class);
|
|
||||||
Program program = programManager.getCurrentProgram();
|
AddressRange range = selection.getFirstRange();// should only be 1
|
||||||
if (program != null) {
|
if (range.getLength() >= (Integer.MAX_VALUE & 0xffffffffL)) {
|
||||||
Memory memory = program.getMemory();
|
Msg.showInfo(getClass(), tool.getActiveWindow(), "Selection Too Large",
|
||||||
MemoryBlock block = memory.getBlock(range.getMinAddress());
|
"The selection is too large to extract.");
|
||||||
String tempFileName = block.getName() + "_[" + range.getMinAddress() + "," +
|
return;
|
||||||
range.getMaxAddress() + "]_";
|
}
|
||||||
try {
|
|
||||||
File tempFile = File.createTempFile(tempFileName, ".tmp");
|
ProgramManager programManager = tool.getService(ProgramManager.class);
|
||||||
OutputStream outputStream = new FileOutputStream(tempFile);
|
Program program = programManager.getCurrentProgram();
|
||||||
try {
|
if (program == null) {
|
||||||
byte[] bytes = new byte[(int) range.getLength()];
|
return;
|
||||||
memory.getBytes(range.getMinAddress(), bytes);
|
}
|
||||||
outputStream.write(bytes);
|
|
||||||
}
|
Memory memory = program.getMemory();
|
||||||
finally {
|
MemoryBlock block = memory.getBlock(range.getMinAddress());
|
||||||
outputStream.close();
|
String tempFileName =
|
||||||
}
|
block.getName() + "_[" + range.getMinAddress() + "," + range.getMaxAddress() + "]_";
|
||||||
DomainFolder folder =
|
try {
|
||||||
AppInfo.getActiveProject().getProjectData().getRootFolder();
|
File tempFile = File.createTempFile(tempFileName, ".tmp");
|
||||||
importFile(folder, tempFile);
|
try (OutputStream outputStream = new FileOutputStream(tempFile)) {
|
||||||
}
|
byte[] bytes = new byte[(int) range.getLength()];
|
||||||
catch (IOException e) {
|
memory.getBytes(range.getMinAddress(), bytes);
|
||||||
Msg.showInfo(getClass(), tool.getActiveWindow(), "I/O Error Occurred",
|
outputStream.write(bytes);
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
package ghidra.plugin.importer;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Window;
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
|
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
|
||||||
|
@ -49,6 +48,12 @@ import ghidra.util.task.TaskLauncher;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
import util.CollectionUtils;
|
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 {
|
public class ImporterUtilities {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,14 +95,17 @@ public class ImporterUtilities {
|
||||||
*/
|
*/
|
||||||
public static void setProgramProperties(Program program, FSRL fsrl, TaskMonitor monitor)
|
public static void setProgramProperties(Program program, FSRL fsrl, TaskMonitor monitor)
|
||||||
throws CancelledException, IOException {
|
throws CancelledException, IOException {
|
||||||
|
|
||||||
|
Objects.requireNonNull(monitor);
|
||||||
|
|
||||||
int id = program.startTransaction("setImportProperties");
|
int id = program.startTransaction("setImportProperties");
|
||||||
try {
|
try {
|
||||||
fsrl = FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
fsrl = FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
||||||
|
|
||||||
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
Options propertyList = program.getOptions(Program.PROGRAM_INFO);
|
||||||
propertyList.setString(ProgramMappingService.PROGRAM_SOURCE_FSRL, fsrl.toString());
|
propertyList.setString(ProgramMappingService.PROGRAM_SOURCE_FSRL, fsrl.toString());
|
||||||
if ((program.getExecutableMD5() == null || program.getExecutableMD5().isEmpty()) &&
|
String md5 = program.getExecutableMD5();
|
||||||
fsrl.getMD5() != null) {
|
if ((md5 == null || md5.isEmpty()) && fsrl.getMD5() != null) {
|
||||||
program.setExecutableMD5(fsrl.getMD5());
|
program.setExecutableMD5(fsrl.getMD5());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,121 +122,156 @@ public class ImporterUtilities {
|
||||||
* <p>
|
* <p>
|
||||||
* If the file is a container of other files, a batch import dialog will be used,
|
* 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.
|
* otherwise the normal single file import dialog will be shown.
|
||||||
* <p>
|
|
||||||
*
|
*
|
||||||
* @param fsrl {@link FSRL} of the file to import.
|
* @param tool {@link PluginTool} will be used as the parent tool for dialogs
|
||||||
* @param destFolder {@link DomainFolder} destination folder where the imported file
|
* @param programManager optional {@link ProgramManager} instance to use to open imported
|
||||||
* will default to. (the user will be able to choose a different location).
|
* binaries with, or null
|
||||||
* @param suggestedDestinationPath optional string path that will automatically be pre-pended
|
* @param fsrl {@link FSRL} of the file to import
|
||||||
* to the destination filename.
|
* @param destinationFolder {@link DomainFolder} destination folder where the imported file
|
||||||
* @param tool {@link PluginTool} will be used as the parent tool for dialogs.
|
* will default to. (the user will be able to choose a different location)
|
||||||
* @param programManager optional {@link ProgramManager} instance to use to open imported binaries with, or null.
|
* @param suggestedPath optional string path that will automatically be pre-pended
|
||||||
|
* to the destination filename
|
||||||
*/
|
*/
|
||||||
public static void showImportDialog(FSRL fsrl, DomainFolder destFolder,
|
public static void showImportDialog(PluginTool tool, ProgramManager programManager, FSRL fsrl,
|
||||||
String suggestedDestinationPath, PluginTool tool, ProgramManager programManager) {
|
DomainFolder destinationFolder, String suggestedPath) {
|
||||||
|
|
||||||
Component parent = tool.getActiveWindow();
|
TaskLauncher.launchModal("Import", monitor -> {
|
||||||
AtomicReference<RefdFile> refdFile = new AtomicReference<>();
|
showImportDialog(tool, programManager, fsrl, destinationFolder, suggestedPath, monitor);
|
||||||
AtomicReference<FSRL> fqFSRL = new AtomicReference<>();
|
});
|
||||||
AtomicBoolean isFSContainer = new AtomicBoolean();
|
}
|
||||||
AtomicBoolean success = new AtomicBoolean();
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
try {
|
||||||
// do IO stuff in separate thread, with no UI prompts
|
FileSystemService service = FileSystemService.getInstance();
|
||||||
TaskLauncher.launchModal("Import", (monitor) -> {
|
referencedFile = service.getRefdFile(fsrl, monitor);
|
||||||
try {
|
|
||||||
RefdFile tmpRefdFile =
|
|
||||||
FileSystemService.getInstance().getRefdFile(fsrl, monitor);
|
|
||||||
refdFile.set(tmpRefdFile);
|
|
||||||
|
|
||||||
FSRL tmpFQFSRL =
|
FSRL fullFsrl = service.getFullyQualifiedFSRL(fsrl, monitor);
|
||||||
FileSystemService.getInstance().getFullyQualifiedFSRL(fsrl, monitor);
|
boolean isFSContainer = service.isFileFilesystemContainer(fullFsrl, monitor);
|
||||||
fqFSRL.set(tmpFQFSRL);
|
if (referencedFile.file.getLength() == 0) {
|
||||||
|
Msg.showError(ImporterUtilities.class, null, "File is empty",
|
||||||
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",
|
|
||||||
"File " + fsrl.getPath() + " is empty, nothing to import");
|
"File " + fsrl.getPath() + " is empty, nothing to import");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GFileSystem fs = refdFile.get().fsRef.getFilesystem();
|
GFileSystem fs = referencedFile.fsRef.getFilesystem();
|
||||||
if (fs instanceof GFileSystemProgramProvider) {
|
if (fs instanceof GFileSystemProgramProvider) {
|
||||||
doFSImport(refdFile.get(), fqFSRL.get(), destFolder, programManager, tool);
|
doFsImport(referencedFile, fullFsrl, destinationFolder, programManager, tool);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFSContainer.get()) {
|
if (!isFSContainer) {
|
||||||
// If file was a container,
|
// normal file; do a single-file import
|
||||||
// allow the user to pick between single import, batch import and fs browser
|
importSingleFile(fullFsrl, destinationFolder, suggestedPath, tool, programManager,
|
||||||
FileSystemBrowserService fsbService =
|
monitor);
|
||||||
tool.getService(FileSystemBrowserService.class);
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
finally {
|
||||||
if (refdFile.get() != null) {
|
close(referencedFile);
|
||||||
try {
|
}
|
||||||
refdFile.get().close();
|
}
|
||||||
}
|
|
||||||
catch (IOException e) {
|
private static void close(Closeable c) {
|
||||||
// ignore
|
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,
|
public static void showAddToProgramDialog(FSRL fsrl, Program program, PluginTool tool,
|
||||||
TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(monitor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ByteProvider provider = FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
ByteProvider provider = FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
||||||
|
|
||||||
if (provider.length() == 0) {
|
if (provider.length() == 0) {
|
||||||
Msg.showWarn(null, null, "Error opening " + fsrl.getName(),
|
Msg.showWarn(null, null, "Error opening " + fsrl.getName(),
|
||||||
"The item does not correspond to a valid file.");
|
"The item does not correspond to a valid file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Loader, Collection<LoadSpec>> loadMap =
|
Map<Loader, Collection<LoadSpec>> loadMap =
|
||||||
LoaderService.getAllSupportedLoadSpecs(provider);
|
LoaderService.getAllSupportedLoadSpecs(provider);
|
||||||
|
|
||||||
|
@ -239,7 +282,7 @@ public class ImporterUtilities {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
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);
|
"I/O error reading " + fsrl.getName(), e);
|
||||||
}
|
}
|
||||||
catch (CancelledException e) {
|
catch (CancelledException e) {
|
||||||
|
@ -250,48 +293,46 @@ public class ImporterUtilities {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@link ImporterDialog} and shows it in the swing thread.
|
* 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
|
* @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)
|
* a more complex file such as a zip file)
|
||||||
* @param defaultFolder the default destination folder for the imported file. Can be null.
|
* @param destinationFolder the default destination folder for the imported file. Can be null
|
||||||
* @param suggestedDestinationPath optional string path that will automatically be pre-pended
|
* @param suggestedPath optional string path that will automatically be pre-pended
|
||||||
* to the destination filename.
|
* to the destination filename
|
||||||
* @param tool the parent UI component
|
* @param tool the parent UI component
|
||||||
* @param programManager optional {@link ProgramManager} instance to open the imported file in.
|
* @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.
|
|
||||||
*/
|
*/
|
||||||
private static void importSingleFile(FSRL fsrl, DomainFolder defaultFolder,
|
private static void importSingleFile(FSRL fsrl, DomainFolder destinationFolder,
|
||||||
String suggestedDestinationPath, PluginTool tool, ProgramManager programManager) {
|
String suggestedPath, PluginTool tool, ProgramManager programManager,
|
||||||
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
TaskLauncher.launchNonModal("Import File", monitor -> {
|
try {
|
||||||
try {
|
|
||||||
ByteProvider provider =
|
|
||||||
FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
|
||||||
Map<Loader, Collection<LoadSpec>> loadMap =
|
|
||||||
LoaderService.getAllSupportedLoadSpecs(provider);
|
|
||||||
|
|
||||||
SystemUtilities.runSwingLater(() -> {
|
ByteProvider provider = FileSystemService.getInstance().getByteProvider(fsrl, monitor);
|
||||||
ImporterDialog importerDialog = new ImporterDialog(tool, programManager,
|
Map<Loader, Collection<LoadSpec>> loadMap =
|
||||||
loadMap, provider, suggestedDestinationPath);
|
LoaderService.getAllSupportedLoadSpecs(provider);
|
||||||
if (defaultFolder != null) {
|
|
||||||
importerDialog.setDestinationFolder(defaultFolder);
|
SystemUtilities.runSwingLater(() -> {
|
||||||
}
|
ImporterDialog importerDialog =
|
||||||
tool.showDialog(importerDialog);
|
new ImporterDialog(tool, programManager, loadMap, provider, suggestedPath);
|
||||||
});
|
if (destinationFolder != null) {
|
||||||
}
|
importerDialog.setDestinationFolder(destinationFolder);
|
||||||
catch (IOException ioe) {
|
}
|
||||||
Msg.showError(ImporterUtilities.class, tool.getActiveWindow(),
|
|
||||||
"Error Importing File", "Error when importing file " + fsrl, ioe);
|
tool.showDialog(importerDialog);
|
||||||
}
|
});
|
||||||
catch (CancelledException e) {
|
}
|
||||||
Msg.info(ImporterUtilities.class, "Import single file " + fsrl + " cancelled");
|
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) {
|
ProgramManager programManager, PluginTool tool) {
|
||||||
TaskLauncher.launchNonModal("Import File (FileSystem specific)", monitor -> {
|
TaskLauncher.launchNonModal("Import File (FileSystem specific)", monitor -> {
|
||||||
GFile gfile = refdFile.file;
|
GFile gfile = refdFile.file;
|
||||||
|
@ -339,20 +380,21 @@ public class ImporterUtilities {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform file import and open using optional programManager
|
* 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 fsrl import file location
|
||||||
* @param destFolder project destination folder
|
* @param destFolder project destination folder
|
||||||
* @param loadSpec import {@link LoadSpec}
|
* @param loadSpec import {@link LoadSpec}
|
||||||
* @param programName program name
|
* @param programName program name
|
||||||
* @param options import options
|
* @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
|
* @param monitor task monitor
|
||||||
*/
|
*/
|
||||||
public static void doSingleImport(FSRL fsrl, DomainFolder destFolder, LoadSpec loadSpec,
|
public static void importSingleFile(PluginTool tool, ProgramManager programManager, FSRL fsrl,
|
||||||
String programName, List<Option> options, PluginTool tool,
|
DomainFolder destFolder, LoadSpec loadSpec, String programName, List<Option> options,
|
||||||
ProgramManager programManager, TaskMonitor monitor) {
|
TaskMonitor monitor) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(monitor);
|
||||||
|
|
||||||
// Do a normal single-file import
|
|
||||||
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
||||||
|
|
||||||
Object consumer = new Object();
|
Object consumer = new Object();
|
||||||
|
@ -392,7 +434,7 @@ public class ImporterUtilities {
|
||||||
firstProgram ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE;
|
firstProgram ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE;
|
||||||
programManager.openProgram(program, openState);
|
programManager.openProgram(program, openState);
|
||||||
}
|
}
|
||||||
ImporterUtilities.setProgramProperties(program, fsrl, monitor);
|
setProgramProperties(program, fsrl, monitor);
|
||||||
ProgramMappingService.createAssociation(fsrl, program);
|
ProgramMappingService.createAssociation(fsrl, program);
|
||||||
importedFilesSet.add(program.getDomainFile());
|
importedFilesSet.add(program.getDomainFile());
|
||||||
}
|
}
|
||||||
|
@ -416,8 +458,11 @@ public class ImporterUtilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void doAddToProgram(FSRL fsrl, LoadSpec loadSpec, List<Option> options,
|
public static void addContentToProgram(PluginTool tool, Program program, FSRL fsrl,
|
||||||
Program program, TaskMonitor monitor, PluginTool tool) {
|
LoadSpec loadSpec, List<Option> options, TaskMonitor monitor) {
|
||||||
|
|
||||||
|
Objects.requireNonNull(monitor);
|
||||||
|
|
||||||
MessageLog messageLog = new MessageLog();
|
MessageLog messageLog = new MessageLog();
|
||||||
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
try (ByteProvider bp = FileSystemService.getInstance().getByteProvider(fsrl, monitor)) {
|
||||||
loadSpec.getLoader().loadInto(bp, loadSpec, options, messageLog, program, monitor,
|
loadSpec.getLoader().loadInto(bp, loadSpec, options, messageLog, program, monitor,
|
||||||
|
|
|
@ -18,7 +18,6 @@ package ghidra.plugins.fsbrowser;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
@ -40,6 +39,7 @@ import ghidra.app.util.opinion.LoaderService;
|
||||||
import ghidra.formats.gfilesystem.*;
|
import ghidra.formats.gfilesystem.*;
|
||||||
import ghidra.framework.main.AppInfo;
|
import ghidra.framework.main.AppInfo;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.plugin.importer.ImporterUtilities;
|
import ghidra.plugin.importer.ImporterUtilities;
|
||||||
import ghidra.plugin.importer.ProgramMappingService;
|
import ghidra.plugin.importer.ProgramMappingService;
|
||||||
import ghidra.plugins.fsbrowser.tasks.GFileSystemExtractAllTask;
|
import ghidra.plugins.fsbrowser.tasks.GFileSystemExtractAllTask;
|
||||||
|
@ -151,28 +151,17 @@ class FSBActionManager {
|
||||||
if (!(contextObject instanceof FSBNode[])) {
|
if (!(contextObject instanceof FSBNode[])) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<FSRL> fsrls = new ArrayList<>();
|
List<FSRL> fsrls = new ArrayList<>();
|
||||||
for (FSBNode node : (FSBNode[]) contextObject) {
|
for (FSBNode node : (FSBNode[]) contextObject) {
|
||||||
FSRL fsrl = node.getFSRL();
|
FSRL fsrl = node.getFSRL();
|
||||||
if ((node instanceof FSBDirNode) || (node instanceof FSBRootNode)) {
|
|
||||||
FSBRootNode rootNode = FSBUtils.getNodesRoot(node);
|
FSRL validated = vaildateFsrl(fsrl, node);
|
||||||
GFileSystem fs = rootNode.getFSRef().getFilesystem();
|
if (validated != null) {
|
||||||
if (fs instanceof GFileSystemProgramProvider) {
|
fsrls.add(validated);
|
||||||
GFile gfile;
|
continue;
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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
|
// 'convert' a file system root node back into its container file
|
||||||
fsrls.add(fsrl.getFS().getContainer());
|
fsrls.add(fsrl.getFS().getContainer());
|
||||||
}
|
}
|
||||||
|
@ -183,6 +172,28 @@ class FSBActionManager {
|
||||||
return fsrls;
|
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) {
|
private FSRL getLoadableFSRLFromContext(ActionContext context) {
|
||||||
if (context == null || !(context.getContextObject() instanceof FSBNode)) {
|
if (context == null || !(context.getContextObject() instanceof FSBNode)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -297,16 +308,17 @@ class FSBActionManager {
|
||||||
|
|
||||||
private void openProgramFromFile(FSRL file, String suggestedDestinationPath) {
|
private void openProgramFromFile(FSRL file, String suggestedDestinationPath) {
|
||||||
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
||||||
if (pm != null) {
|
if (pm == null) {
|
||||||
AtomicBoolean success = new AtomicBoolean();
|
return;
|
||||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
|
||||||
success.set(doOpenProgramFromFile(file, suggestedDestinationPath, pm, monitor));
|
|
||||||
});
|
|
||||||
if (!success.get()) {
|
|
||||||
ImporterUtilities.showImportDialog(file, null, suggestedDestinationPath,
|
|
||||||
plugin.getTool(), pm);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
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
|
* 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
|
* be searched for and the user may be prompted for confirmation, or warned if
|
||||||
* no PM found.
|
* no PM found.
|
||||||
* <p>
|
|
||||||
* Package visible only.
|
|
||||||
*
|
*
|
||||||
* @param files List of {@link FSRL} files to open in the active {@link ProgramManager}.
|
* @param files List of {@link FSRL} files to open in the active {@link ProgramManager}.
|
||||||
*/
|
*/
|
||||||
private void openProgramsFromFiles(List<FSRL> files) {
|
private void openProgramsFromFiles(List<FSRL> files) {
|
||||||
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
ProgramManager pm = FSBUtils.getProgramManager(plugin.getTool(), false);
|
||||||
List<FSRL> unmatchedFiles = new ArrayList<>();
|
if (pm == null) {
|
||||||
if (pm != null) {
|
return;
|
||||||
TaskLauncher.launchModal("Open Programs", monitor -> {
|
}
|
||||||
List<FSRL> tmpUnmatchedFiles = doOpenProgramsFromFiles(files, pm, monitor);
|
|
||||||
unmatchedFiles.addAll(tmpUnmatchedFiles);
|
TaskLauncher.launchModal("Open Programs", monitor -> {
|
||||||
});
|
List<FSRL> unmatchedFiles = doOpenProgramsFromFiles(files, pm, monitor);
|
||||||
|
|
||||||
if (unmatchedFiles.size() == 1) {
|
if (unmatchedFiles.size() == 1) {
|
||||||
ImporterUtilities.showImportDialog(unmatchedFiles.get(0), null, null,
|
ImporterUtilities.showImportDialog(plugin.getTool(), pm, unmatchedFiles.get(0),
|
||||||
plugin.getTool(), pm);
|
null, null, monitor);
|
||||||
}
|
}
|
||||||
else if (unmatchedFiles.size() > 1) {
|
else if (unmatchedFiles.size() > 1) {
|
||||||
BatchImportDialog.showAndImport(plugin.getTool(), null, unmatchedFiles, null, pm);
|
BatchImportDialog.showAndImport(plugin.getTool(), null, unmatchedFiles, null, pm);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -420,7 +432,8 @@ class FSBActionManager {
|
||||||
ProgramManager programManager, TaskMonitor monitor, int programsOpened) {
|
ProgramManager programManager, TaskMonitor monitor, int programsOpened) {
|
||||||
boolean doSearch = isProjectSmallEnoughToSearchWithoutWarningUser() ||
|
boolean doSearch = isProjectSmallEnoughToSearchWithoutWarningUser() ||
|
||||||
OptionDialog.showYesNoDialog(null, "Search Project for matching programs?",
|
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 =
|
Map<FSRL, DomainFile> matchedFSRLs =
|
||||||
doSearch ? ProgramMappingService.searchProjectForMatchingFiles(fsrlList, monitor)
|
doSearch ? ProgramMappingService.searchProjectForMatchingFiles(fsrlList, monitor)
|
||||||
|
@ -1081,13 +1094,17 @@ class FSBActionManager {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
FSRL fsrl = getLoadableFSRLFromContext(context);
|
FSRL fsrl = getLoadableFSRLFromContext(context);
|
||||||
if (fsrl != null) {
|
if (fsrl == null) {
|
||||||
String treePath = getFormattedTreePath(context);
|
return;
|
||||||
String suggestedDestinationPath =
|
|
||||||
FilenameUtils.getFullPathNoEndSeparator(treePath).replaceAll(":/", "/");
|
|
||||||
ImporterUtilities.showImportDialog(fsrl, null, suggestedDestinationPath,
|
|
||||||
plugin.getTool(), FSBUtils.getProgramManager(plugin.getTool(), false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@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;
|
protected JButton dismissButton;
|
||||||
private boolean isAlerting;
|
private boolean isAlerting;
|
||||||
private JLabel statusLabel;
|
private JLabel statusLabel;
|
||||||
private JLabel subStatusLabel;
|
|
||||||
private JPanel statusProgPanel; // contains status panel and progress panel
|
private JPanel statusProgPanel; // contains status panel and progress panel
|
||||||
private Timer showTimer;
|
private Timer showTimer;
|
||||||
private TaskScheduler taskScheduler;
|
private TaskScheduler taskScheduler;
|
||||||
|
@ -609,11 +608,6 @@ public class DialogComponentProvider
|
||||||
public void setStatusText(String text) {
|
public void setStatusText(String text) {
|
||||||
setStatusText(text, MessageType.INFO);
|
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
|
* 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);
|
setStatusText(message, type, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSubStatusText(String message, MessageType type) {
|
|
||||||
setSubStatusText(message, type, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setStatusText(String message, MessageType type, boolean alert) {
|
public void setStatusText(String message, MessageType type, boolean alert) {
|
||||||
|
|
||||||
String text = StringUtils.isBlank(message) ? " " : message;
|
String text = StringUtils.isBlank(message) ? " " : message;
|
||||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> doSetStatusText(text, type, alert));
|
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) {
|
private void doSetStatusText(String text, MessageType type, boolean alert) {
|
||||||
|
|
||||||
|
@ -659,14 +641,12 @@ public class DialogComponentProvider
|
||||||
alertMessage();
|
alertMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSetSubStatusText(String text, MessageType type, boolean alert) {
|
private void doSetSubStatusText(String text, MessageType type, boolean alert) {
|
||||||
|
|
||||||
SystemUtilities.assertThisIsTheSwingThread(
|
SystemUtilities.assertThisIsTheSwingThread(
|
||||||
"Setting text must be performed on the Swing thread");
|
"Setting text must be performed on the Swing thread");
|
||||||
|
|
||||||
subStatusLabel.setText(text);
|
|
||||||
subStatusLabel.setForeground(getStatusColor(type));
|
|
||||||
updateStatusToolTip();
|
updateStatusToolTip();
|
||||||
|
|
||||||
if (alert) {
|
if (alert) {
|
||||||
|
@ -711,13 +691,11 @@ public class DialogComponentProvider
|
||||||
// normal Swing mechanism may not have yet happened).
|
// normal Swing mechanism may not have yet happened).
|
||||||
mainPanel.validate();
|
mainPanel.validate();
|
||||||
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
|
statusLabel.setVisible(false); // disable painting in this dialog so we don't see double
|
||||||
subStatusLabel.setVisible(false);
|
|
||||||
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
|
Animator animator = AnimationUtils.pulseComponent(statusLabel, 1);
|
||||||
animator.addTarget(new TimingTargetAdapter() {
|
animator.addTarget(new TimingTargetAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void end() {
|
public void end() {
|
||||||
statusLabel.setVisible(true);
|
statusLabel.setVisible(true);
|
||||||
subStatusLabel.setVisible(true);
|
|
||||||
alertFinishedCallback.call();
|
alertFinishedCallback.call();
|
||||||
isAlerting = false;
|
isAlerting = false;
|
||||||
}
|
}
|
||||||
|
@ -828,7 +806,6 @@ public class DialogComponentProvider
|
||||||
public void clearStatusText() {
|
public void clearStatusText() {
|
||||||
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
|
||||||
statusLabel.setText(" ");
|
statusLabel.setText(" ");
|
||||||
subStatusLabel.setText(" ");
|
|
||||||
updateStatusToolTip();
|
updateStatusToolTip();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -842,15 +819,6 @@ public class DialogComponentProvider
|
||||||
return statusLabel.getText();
|
return statusLabel.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the secondary status message
|
|
||||||
*
|
|
||||||
* @return the secondary status message
|
|
||||||
*/
|
|
||||||
public String getSubStatusText() {
|
|
||||||
return subStatusLabel.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected JLabel getStatusLabel() {
|
protected JLabel getStatusLabel() {
|
||||||
return statusLabel;
|
return statusLabel;
|
||||||
}
|
}
|
||||||
|
@ -941,23 +909,13 @@ public class DialogComponentProvider
|
||||||
updateStatusToolTip();
|
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
|
// use a strut panel so the size of the message area does not change if we make
|
||||||
// the message label not visible
|
// the message label not visible
|
||||||
int height =
|
int height = statusLabel.getPreferredSize().height;
|
||||||
statusLabel.getPreferredSize().height + subStatusLabel.getPreferredSize().height + 5;
|
|
||||||
|
|
||||||
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
|
panel.add(Box.createVerticalStrut(height), BorderLayout.WEST);
|
||||||
panel.add(statusLabel, BorderLayout.CENTER);
|
panel.add(statusLabel, BorderLayout.CENTER);
|
||||||
panel.add(subStatusLabel, BorderLayout.SOUTH);
|
|
||||||
return panel;
|
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)
|
* .setStatusTextAlignment(SwingConstants.LEADING)
|
||||||
* .launchModal();
|
* .launchModal();
|
||||||
* </pre>
|
* </pre>
|
||||||
|
*
|
||||||
|
* Or,
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* TaskBuilder.withRunnable(monitor -> doWork(parameter, monitor))
|
||||||
|
* .setTitle("Task Title")
|
||||||
|
* .setHasProgress(true)
|
||||||
|
* .setCanCancel(true)
|
||||||
|
* .setStatusTextAlignment(SwingConstants.LEADING)
|
||||||
|
* .launchModal();
|
||||||
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class TaskBuilder {
|
public class TaskBuilder {
|
||||||
|
|
||||||
|
@ -57,7 +68,25 @@ public class TaskBuilder {
|
||||||
private int statusTextAlignment = SwingConstants.CENTER;
|
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
|
* @param title the required title for your task. This will appear as the title of the
|
||||||
* task dialog
|
* task dialog
|
||||||
|
@ -68,6 +97,18 @@ public class TaskBuilder {
|
||||||
this.runnable = Objects.requireNonNull(runnable);
|
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>.
|
* 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.
|
* Launches the task built by this builder, using a blocking modal dialog.
|
||||||
*/
|
*/
|
||||||
public void launchModal() {
|
public void launchModal() {
|
||||||
|
validate();
|
||||||
|
|
||||||
boolean isModal = true;
|
boolean isModal = true;
|
||||||
Task t = new TaskBuilderTask(isModal);
|
Task t = new TaskBuilderTask(isModal);
|
||||||
int delay = getDelay(launchDelay, isModal);
|
int delay = getDelay(launchDelay, isModal);
|
||||||
|
@ -160,6 +203,8 @@ public class TaskBuilder {
|
||||||
* @return the launcher that launched the task
|
* @return the launcher that launched the task
|
||||||
*/
|
*/
|
||||||
public TaskLauncher launchNonModal() {
|
public TaskLauncher launchNonModal() {
|
||||||
|
validate();
|
||||||
|
|
||||||
boolean isModal = false;
|
boolean isModal = false;
|
||||||
Task t = new TaskBuilderTask(isModal);
|
Task t = new TaskBuilderTask(isModal);
|
||||||
int delay = getDelay(launchDelay, isModal);
|
int delay = getDelay(launchDelay, isModal);
|
||||||
|
@ -167,6 +212,12 @@ public class TaskBuilder {
|
||||||
return launcher;
|
return launcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validate() {
|
||||||
|
if (title == null) {
|
||||||
|
throw new NullPointerException("Task title cannot be null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static int getDelay(int userDelay, boolean isModal) {
|
private static int getDelay(int userDelay, boolean isModal) {
|
||||||
if (userDelay >= 0) {
|
if (userDelay >= 0) {
|
||||||
return userDelay;
|
return userDelay;
|
||||||
|
|
|
@ -17,7 +17,6 @@ package ghidra.util.task;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
@ -38,10 +37,10 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
|
|
||||||
/** Timer used to give the task a chance to complete */
|
/** Timer used to give the task a chance to complete */
|
||||||
private static final int SLEEPY_TIME = 10;
|
private static final int SLEEPY_TIME = 10;
|
||||||
|
|
||||||
/** Amount of time to wait before showing the monitor dialog */
|
/** Amount of time to wait before showing the monitor dialog */
|
||||||
private final static int MAX_DELAY = 200000;
|
private final static int MAX_DELAY = 200000;
|
||||||
|
|
||||||
public final static int DEFAULT_WIDTH = 275;
|
public final static int DEFAULT_WIDTH = 275;
|
||||||
|
|
||||||
private Timer showTimer;
|
private Timer showTimer;
|
||||||
|
@ -57,24 +56,9 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
/** Runnable that updates the primary message label in the dialog */
|
/** Runnable that updates the primary message label in the dialog */
|
||||||
private Runnable updatePrimaryMessageRunnable;
|
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 */
|
/** If not null, then the value of the string has yet to be rendered */
|
||||||
private String newPrimaryMessage;
|
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
|
* Constructor
|
||||||
*
|
*
|
||||||
|
@ -108,7 +92,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) {
|
public TaskDialog(String title, boolean canCancel, boolean isModal, boolean hasProgress) {
|
||||||
this(null, title, isModal, canCancel, hasProgress);
|
this(null, title, isModal, canCancel, hasProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
|
@ -126,20 +110,10 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
setup(canCancel, hasProgress);
|
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) {
|
private void setup(boolean canCancel, boolean hasProgress) {
|
||||||
monitorComponent = new TaskMonitorComponent(false, false);
|
monitorComponent = new TaskMonitorComponent(false, false);
|
||||||
chompingBitsPanel = new ChompingBitsAnimationPanel();
|
chompingBitsPanel = new ChompingBitsAnimationPanel();
|
||||||
|
|
||||||
setCancelEnabled(canCancel);
|
setCancelEnabled(canCancel);
|
||||||
setRememberLocation(false);
|
setRememberLocation(false);
|
||||||
setRememberSize(false);
|
setRememberSize(false);
|
||||||
|
@ -154,12 +128,6 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
newPrimaryMessage = null;
|
newPrimaryMessage = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updateSecondaryMessageRunnable = () -> {
|
|
||||||
setSubStatusText(newSecondaryMessage);
|
|
||||||
synchronized (TaskDialog.this) {
|
|
||||||
newSecondaryMessage = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
shouldCancelRunnable = () -> {
|
shouldCancelRunnable = () -> {
|
||||||
int currentTaskID = taskID.get();
|
int currentTaskID = taskID.get();
|
||||||
|
|
||||||
|
@ -171,7 +139,7 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
|
|
||||||
mainPanel = new JPanel(new BorderLayout());
|
mainPanel = new JPanel(new BorderLayout());
|
||||||
addWorkPanel(mainPanel);
|
addWorkPanel(mainPanel);
|
||||||
|
|
||||||
if (hasProgress) {
|
if (hasProgress) {
|
||||||
installProgressMonitor();
|
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
|
@Override
|
||||||
public void setCancelEnabled(boolean enable) {
|
public void setCancelEnabled(boolean enable) {
|
||||||
monitorComponent.setCancelEnabled(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.
|
// only to show a progress dialog if enough time has elapsed.
|
||||||
//
|
//
|
||||||
GTimer.scheduleRunnable(delay, () -> {
|
GTimer.scheduleRunnable(delay, () -> {
|
||||||
|
|
||||||
if (isCompleted()) {
|
if (isCompleted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -467,12 +421,4 @@ public class TaskDialog extends DialogComponentProvider implements TaskMonitor {
|
||||||
public void removeCancelledListener(CancelledListener listener) {
|
public void removeCancelledListener(CancelledListener listener) {
|
||||||
monitorComponent.removeCancelledListener(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 javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import generic.util.WindowUtilities;
|
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
|
* 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) {
|
private TaskDialog buildTaskDialog(Component comp, int dialogWidth) {
|
||||||
|
|
||||||
//
|
taskDialog = createTaskDialog(comp);
|
||||||
// This class may be used by background threads. Make sure that our GUI creation is
|
taskDialog.setMinimumSize(dialogWidth, 0);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (task.isInterruptible() || task.isForgettable()) {
|
if (task.isInterruptible() || task.isForgettable()) {
|
||||||
taskDialog.addCancelledListener(monitorChangeListener);
|
taskDialog.addCancelledListener(monitorChangeListener);
|
||||||
|
|
|
@ -23,12 +23,10 @@ import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import docking.test.AbstractDockingTest;
|
import docking.test.AbstractDockingTest;
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
|
|
||||||
public class TaskDialogTest extends AbstractDockingTest {
|
public class TaskDialogTest extends AbstractDockingTest {
|
||||||
|
|
||||||
|
@ -95,105 +93,53 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||||
assertTrue(dialogSpy.wasShown());
|
assertTrue(dialogSpy.wasShown());
|
||||||
assertSwingThreadFinishedBeforeTask();
|
assertSwingThreadFinishedBeforeTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verifies that if the dialog cancel button is activated, the task is cancelled
|
* Verifies that if the dialog cancel button is activated, the task is cancelled
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTaskCancel() throws Exception {
|
public void testTaskCancel() throws Exception {
|
||||||
SlowModalTask task = new SlowModalTask();
|
SlowModalTask task = new SlowModalTask();
|
||||||
TaskDialogSpy dialogSpy = launchTask(task);
|
TaskDialogSpy dialogSpy = launchTask(task);
|
||||||
|
|
||||||
dialogSpy.doShow();
|
dialogSpy.doShow();
|
||||||
|
|
||||||
waitForTask();
|
waitForTask();
|
||||||
|
|
||||||
assertFalse(dialogSpy.isCancelled());
|
assertFalse(dialogSpy.isCancelled());
|
||||||
dialogSpy.cancel();
|
dialogSpy.cancel();
|
||||||
assertTrue(dialogSpy.isCancelled());
|
assertTrue(dialogSpy.isCancelled());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verifies that if the task does not allow cancellation, the cancel button on the GUI
|
* Verifies that if the task does not allow cancellation, the cancel button on the GUI
|
||||||
* is disabled
|
* is disabled
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testTaskNoCancel() throws Exception {
|
public void testTaskNoCancel() throws Exception {
|
||||||
SlowModalTask task = new SlowModalTask();
|
SlowModalTask task = new SlowModalTask();
|
||||||
TaskDialogSpy dialogSpy = launchTask(task);
|
TaskDialogSpy dialogSpy = launchTask(task);
|
||||||
|
|
||||||
dialogSpy.doShow();
|
dialogSpy.doShow();
|
||||||
dialogSpy.setCancelEnabled(false);
|
dialogSpy.setCancelEnabled(false);
|
||||||
|
|
||||||
waitForTask();
|
waitForTask();
|
||||||
|
|
||||||
assertFalse(dialogSpy.isCancelEnabled());
|
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() {
|
private void assertSwingThreadBlockedForTask() {
|
||||||
TDEvent lastEvent = eventQueue.peekLast();
|
TDEvent lastEvent = eventQueue.peekLast();
|
||||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||||
if (!swingIsLast) {
|
if (!swingIsLast) {
|
||||||
System.out.println("Events " + eventQueue);
|
|
||||||
fail("The Swing thread did not block until the task finished");
|
fail("The Swing thread did not block until the task finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSwingThreadFinishedBeforeTask() {
|
private void assertSwingThreadFinishedBeforeTask() {
|
||||||
int size = eventQueue.size();
|
|
||||||
TDEvent lastEvent = eventQueue.peekLast();
|
TDEvent lastEvent = eventQueue.peekLast();
|
||||||
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
boolean swingIsLast = lastEvent.getThreadName().contains("AWT");
|
||||||
if (swingIsLast) {
|
if (swingIsLast) {
|
||||||
System.out.println("Events (" + size + ")\n\t" + StringUtils.join(eventQueue, "\n\t"));
|
|
||||||
fail("The Swing thread blocked until the task finished");
|
fail("The Swing thread blocked until the task finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,7 +190,7 @@ public class TaskDialogTest extends AbstractDockingTest {
|
||||||
public TaskDialogSpy(Task task) {
|
public TaskDialogSpy(Task task) {
|
||||||
super(task);
|
super(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doShow() {
|
protected void doShow() {
|
||||||
shown.set(true);
|
shown.set(true);
|
||||||
|
|
|
@ -41,35 +41,10 @@ public interface StatusListener {
|
||||||
* @param alert true to grab the user's attention
|
* @param alert true to grab the user's attention
|
||||||
*/
|
*/
|
||||||
void setStatusText(String text, MessageType type, boolean alert);
|
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("")
|
* Clear the current status - same as setStatusText("")
|
||||||
* without being recorded
|
* without being recorded
|
||||||
*/
|
*/
|
||||||
void clearStatusText();
|
void clearStatusText();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,7 @@ import ghidra.util.*;
|
||||||
import ghidra.util.exception.CancelledException;
|
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 {
|
public abstract class Task implements MonitoredRunnable {
|
||||||
private String title;
|
private String title;
|
||||||
|
@ -37,7 +35,7 @@ public abstract class Task implements MonitoredRunnable {
|
||||||
private boolean isForgettable;
|
private boolean isForgettable;
|
||||||
protected boolean waitForTaskCompleted = false;
|
protected boolean waitForTaskCompleted = false;
|
||||||
private Set<TaskListener> listeners = new HashSet<>();
|
private Set<TaskListener> listeners = new HashSet<>();
|
||||||
private TaskMonitor taskMonitor;
|
protected TaskMonitor taskMonitor = TaskMonitor.DUMMY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new Task.
|
* Creates new Task.
|
||||||
|
@ -70,9 +68,9 @@ public abstract class Task implements MonitoredRunnable {
|
||||||
* progress indicator
|
* progress indicator
|
||||||
* @param isModal true means that the dialog is modal and the task has to
|
* @param isModal true means that the dialog is modal and the task has to
|
||||||
* complete or be canceled before any other action can occur
|
* complete or be canceled before any other action can occur
|
||||||
* @param waitForTaskCompleted true causes the running thread to block until the finish or cancelled
|
* @param waitForTaskCompleted true causes the running thread to block until the finish or
|
||||||
* callback has completed on the swing thread. Note: passing true only makes sense if the
|
* cancelled callback has completed on the swing thread. Note: passing true
|
||||||
* task is modal.
|
* only makes sense if the task is modal.
|
||||||
*/
|
*/
|
||||||
public Task(String title, boolean canCancel, boolean hasProgress, boolean isModal,
|
public Task(String title, boolean canCancel, boolean hasProgress, boolean isModal,
|
||||||
boolean waitForTaskCompleted) {
|
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,
|
* 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
|
* starting the thread causes the object's <code>run</code> method to be called in that
|
||||||
* separately executing thread.
|
* 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
|
* @param monitor the task monitor
|
||||||
*/
|
*/
|
||||||
|
@ -123,8 +118,6 @@ public abstract class Task implements MonitoredRunnable {
|
||||||
public final void monitoredRun(TaskMonitor monitor) {
|
public final void monitoredRun(TaskMonitor monitor) {
|
||||||
this.taskMonitor = monitor;
|
this.taskMonitor = monitor;
|
||||||
|
|
||||||
int monitorId = TaskMonitorService.register(monitor);
|
|
||||||
|
|
||||||
// this will be removed from SystemUtilities in Task.run() after the task is finished
|
// this will be removed from SystemUtilities in Task.run() after the task is finished
|
||||||
TaskUtilities.addTrackedTask(this, monitor);
|
TaskUtilities.addTrackedTask(this, monitor);
|
||||||
|
|
||||||
|
@ -141,14 +134,8 @@ public abstract class Task implements MonitoredRunnable {
|
||||||
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
|
getTaskTitle() + " - Uncaught Exception: " + t.toString(), t);
|
||||||
}
|
}
|
||||||
finally {
|
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);
|
TaskUtilities.removeTrackedTask(this);
|
||||||
this.taskMonitor = null;
|
this.taskMonitor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyTaskListeners(isCancelled);
|
notifyTaskListeners(isCancelled);
|
||||||
|
|
|
@ -27,8 +27,7 @@ import generic.jar.ResourceFile;
|
||||||
import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider;
|
import ghidra.app.plugin.processors.sleigh.SleighLanguageProvider;
|
||||||
import ghidra.program.model.lang.*;
|
import ghidra.program.model.lang.*;
|
||||||
import ghidra.util.classfinder.ClassSearcher;
|
import ghidra.util.classfinder.ClassSearcher;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskBuilder;
|
||||||
import ghidra.util.task.TaskMonitorService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Language service used gather up all the languages that were found
|
* 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() {
|
private void searchForProviders() {
|
||||||
TaskMonitor monitor = TaskMonitorService.getMonitor();
|
Set<LanguageProvider> languageProviders =
|
||||||
monitor.setMessage("Searching for language providers");
|
ClassSearcher.getInstances(LanguageProvider.class);
|
||||||
try {
|
|
||||||
Set<LanguageProvider> languageProviders =
|
|
||||||
ClassSearcher.getInstances(LanguageProvider.class);
|
|
||||||
|
|
||||||
searchCompleted = true;
|
searchCompleted = true;
|
||||||
processProviders(languageProviders);
|
|
||||||
}
|
//@formatter:off
|
||||||
finally {
|
TaskBuilder.withRunnable(monitor -> {
|
||||||
monitor.finished();
|
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
|
@Override
|
||||||
public Language getLanguage(LanguageID languageID) throws LanguageNotFoundException {
|
public Language getLanguage(LanguageID languageID) throws LanguageNotFoundException {
|
||||||
|
LanguageInfo info = languageMap.get(languageID);
|
||||||
TaskMonitor monitor = TaskMonitorService.getMonitor();
|
if (info == null) {
|
||||||
monitor.setMessage("Retrieving language: " + languageID);
|
throw new LanguageNotFoundException(languageID);
|
||||||
try {
|
|
||||||
LanguageInfo info = languageMap.get(languageID);
|
|
||||||
|
|
||||||
if (info == null) {
|
|
||||||
throw new LanguageNotFoundException(languageID);
|
|
||||||
}
|
|
||||||
|
|
||||||
Language lang = info.getLanguage();
|
|
||||||
return lang;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
monitor.finished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@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();
|
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
|
* 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
|
* @param message the message to display
|
||||||
*/
|
*/
|
||||||
public void setMessage(String message);
|
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
|
* 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