mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-01-09 14:08:03 -05:00
Updated ImporterDialog to remember the last used folder
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -30,16 +30,17 @@ public interface FileImporterService {
|
||||
|
||||
/**
|
||||
* Imports the given file into the specified Ghidra project folder.
|
||||
* @param folder the Ghidra project folder to store the imported file.
|
||||
* If null, the active project's root folder will be assumed.
|
||||
* @param folder the folder to use as the destination for the import. If the value is null,
|
||||
* then the last used folder is preferred, with the root folder being used by default.
|
||||
* @param file the file to import.
|
||||
*/
|
||||
public void importFile(DomainFolder folder, File file);
|
||||
|
||||
/**
|
||||
* Imports the given files into the specified Ghidra project folder.
|
||||
* @param folder the Ghidra project folder to store the imported files.
|
||||
* If null, the active project's root folder will be assumed.
|
||||
* @param folder the Ghidra project folder to store the imported files. The folder to use as
|
||||
* the destination for the import. If the value is null, then the last used folder is
|
||||
* preferred, with the root folder being used by default.
|
||||
* @param files the files to import.
|
||||
*/
|
||||
public void importFiles(DomainFolder folder, List<File> files);
|
||||
|
||||
@@ -65,6 +65,8 @@ import ghidra.util.task.TaskBuilder;
|
||||
*/
|
||||
public class ImporterDialog extends DialogComponentProvider {
|
||||
|
||||
private static final String PREFERENCES_LAST_FOLDER = "IMPORTER_DIALOG_LAST_FOLDER";
|
||||
|
||||
protected PluginTool tool;
|
||||
private ProgramManager programManager;
|
||||
protected FSRL fsrl;
|
||||
@@ -131,7 +133,8 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||
setDefaultButton(okButton);
|
||||
setOkEnabled(false);
|
||||
|
||||
setDestinationFolder(getProjectRootFolder());
|
||||
DomainFolder folder = initializeDestinationFolder();
|
||||
setDestinationFolder(folder);
|
||||
selectedLoaderChanged();
|
||||
setMinimumSize(new Dimension(500, getPreferredSize().height));
|
||||
setRememberSize(false);
|
||||
@@ -367,6 +370,15 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||
//@formatter:on
|
||||
|
||||
close();
|
||||
|
||||
saveLastUsedFolder();
|
||||
}
|
||||
|
||||
private void saveLastUsedFolder() {
|
||||
|
||||
String path = destinationFolder.getPathname();
|
||||
Preferences.setProperty(PREFERENCES_LAST_FOLDER, path);
|
||||
Preferences.store();
|
||||
}
|
||||
|
||||
private String removeTrailingSlashes(String path) {
|
||||
@@ -586,15 +598,26 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||
return preferredSpecPair;
|
||||
}
|
||||
|
||||
private DomainFolder getProjectRootFolder() {
|
||||
private DomainFolder initializeDestinationFolder() {
|
||||
Project project = AppInfo.getActiveProject();
|
||||
ProjectData projectData = project.getProjectData();
|
||||
String lastFolderPath = Preferences.getProperty(PREFERENCES_LAST_FOLDER);
|
||||
if (lastFolderPath == null) {
|
||||
return projectData.getRootFolder();
|
||||
}
|
||||
|
||||
DomainFolder folder = projectData.getFolder(lastFolderPath);
|
||||
if (folder != null) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
return projectData.getRootFolder();
|
||||
}
|
||||
|
||||
private void chooseProjectFolder() {
|
||||
JComponent component = getComponent();
|
||||
DataTreeDialog dataTreeDialog =
|
||||
new DataTreeDialog(getComponent(), "Choose a project folder", CHOOSE_FOLDER);
|
||||
new DataTreeDialog(component, "Choose a Project Folder", CHOOSE_FOLDER);
|
||||
dataTreeDialog.setSelectedFolder(destinationFolder);
|
||||
dataTreeDialog.showComponent();
|
||||
DomainFolder folder = dataTreeDialog.getDomainFolder();
|
||||
@@ -603,9 +626,10 @@ public class ImporterDialog extends DialogComponentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// Methods for testing ///
|
||||
///////////////////////////////////////
|
||||
public DomainFolder getDestinationFolder() {
|
||||
return destinationFolder;
|
||||
}
|
||||
|
||||
JComboBox<Loader> getFormatComboBox() {
|
||||
return loaderComboBox;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,14 @@ import docking.action.*;
|
||||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.FileImporterService;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.util.Option;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.opinion.*;
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
@@ -42,8 +43,7 @@ import ghidra.formats.gfilesystem.FileCache.FileCacheEntry;
|
||||
import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder;
|
||||
import ghidra.formats.gfilesystem.FileSystemService;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.main.datatree.DataTree;
|
||||
import ghidra.framework.main.datatree.JavaFileListHandler;
|
||||
import ghidra.framework.main.datatree.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
@@ -52,6 +52,7 @@ import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.store.local.ItemDeserializer;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.plugins.importer.batch.BatchImportDialog;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressRange;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.*;
|
||||
@@ -169,37 +170,28 @@ public class ImporterPlugin extends Plugin
|
||||
if (loadSpec == null) {
|
||||
return false;
|
||||
}
|
||||
return loadSpec.getLoader()
|
||||
.getDefaultOptions(provider, loadSpec, null, false, false)
|
||||
.stream()
|
||||
.anyMatch(e -> e.getName()
|
||||
.equals(AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME));
|
||||
|
||||
String option = AbstractLibrarySupportLoader.LOAD_ONLY_LIBRARIES_OPTION_NAME;
|
||||
Loader loader = loadSpec.getLoader();
|
||||
List<Option> options = loader.getDefaultOptions(provider, loadSpec, null, false, false);
|
||||
return options.stream().anyMatch(e -> e.getName().equals(option));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importFiles(DomainFolder destFolder, List<File> files) {
|
||||
|
||||
if (destFolder == null) {
|
||||
destFolder = tool.getProject().getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
files = handleSimpleDBUnpack(destFolder, files);
|
||||
if (files.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<FSRL> fsrls = files.stream().map(f -> fsService().getLocalFSRL(f)).toList();
|
||||
BatchImportDialog.showAndImport(tool, null, fsrls, destFolder,
|
||||
getTool().getService(ProgramManager.class));
|
||||
ProgramManager programManager = getTool().getService(ProgramManager.class);
|
||||
BatchImportDialog.showAndImport(tool, null, fsrls, destFolder, programManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importFile(DomainFolder folder, File file) {
|
||||
|
||||
if (folder == null) {
|
||||
folder = tool.getProject().getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
if (handleSimpleDBUnpack(folder, file)) {
|
||||
return;
|
||||
}
|
||||
@@ -234,52 +226,34 @@ public class ImporterPlugin extends Plugin
|
||||
return name;
|
||||
}
|
||||
|
||||
private List<File> handleSimpleDBUnpack(DomainFolder folder, List<File> files) {
|
||||
private boolean handleSimpleDBUnpack(DomainFolder folder, File file) {
|
||||
List<File> files = handleSimpleDBUnpack(folder, List.of(file));
|
||||
return files.isEmpty();
|
||||
}
|
||||
|
||||
private List<File> handleSimpleDBUnpack(DomainFolder destinationFolder, List<File> files) {
|
||||
if (frontEndService == null || !isSimpleUnpackEnabled()) {
|
||||
return files;
|
||||
}
|
||||
|
||||
ArrayList<File> remainingFiles = new ArrayList<>();
|
||||
if (destinationFolder == null) {
|
||||
Project project = tool.getProject();
|
||||
ProjectData projectData = project.getProjectData();
|
||||
destinationFolder = projectData.getRootFolder();
|
||||
}
|
||||
|
||||
Task task = new Task("", true, true, true) {
|
||||
DomainFolder folder = destinationFolder;
|
||||
List<File> failedFiles = new ArrayList<>();
|
||||
Task task = new Task("Unpack Files", true, true, true) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
for (File f : files) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
// Test for Packed DB file using ItemDeserializer
|
||||
ItemDeserializer itemDeserializer = null;
|
||||
try {
|
||||
itemDeserializer = new ItemDeserializer(f); // fails for non-packed file
|
||||
}
|
||||
catch (IOException e) {
|
||||
remainingFiles.add(f);
|
||||
continue; // not a Packed DB - skip file
|
||||
}
|
||||
finally {
|
||||
if (itemDeserializer != null) {
|
||||
itemDeserializer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
monitor.setMessage("Unpacking " + f.getName() + " ...");
|
||||
|
||||
// Perform direct unpack of Packed DB file
|
||||
String filename = makeValidUniqueFilename(f.getName(), folder);
|
||||
try {
|
||||
DomainFile df = folder.createFile(filename, f, monitor);
|
||||
Msg.info(this, "Imported " + f.getName() + " to " + df.getPathname());
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertException(e); // unexpected - valid name was used
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JavaFileListHandler.class, tool.getToolFrame(),
|
||||
"Packed DB Import Failed", "Failed to import " + f.getName(), e);
|
||||
if (!doUnpackFile(folder, monitor, f)) {
|
||||
failedFiles.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -288,12 +262,43 @@ public class ImporterPlugin extends Plugin
|
||||
return List.of(); // return empty list if cancelled
|
||||
}
|
||||
|
||||
return remainingFiles; // return files not yet imported
|
||||
return failedFiles; // return files not yet imported
|
||||
}
|
||||
|
||||
private boolean handleSimpleDBUnpack(DomainFolder folder, File file) {
|
||||
List<File> files = handleSimpleDBUnpack(folder, List.of(file));
|
||||
return files.isEmpty();
|
||||
private boolean doUnpackFile(DomainFolder folder, TaskMonitor monitor, File f)
|
||||
throws CancelledException {
|
||||
|
||||
// Test for Packed DB file using ItemDeserializer
|
||||
ItemDeserializer deserializer = null;
|
||||
try {
|
||||
deserializer = new ItemDeserializer(f); // fails for non-packed file
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
if (deserializer != null) {
|
||||
deserializer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
monitor.setMessage("Unpacking " + f.getName() + " ...");
|
||||
|
||||
// Perform direct unpack of Packed DB file
|
||||
String filename = makeValidUniqueFilename(f.getName(), folder);
|
||||
try {
|
||||
DomainFile df = folder.createFile(filename, f, monitor);
|
||||
Msg.info(this, "Imported " + f.getName() + " to " + df.getPathname());
|
||||
return true;
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertException(e); // unexpected - valid name was used
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(JavaFileListHandler.class, tool.getToolFrame(),
|
||||
"Packed DB Import Failed", "Failed to import " + f.getName(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSimpleUnpackEnabled() {
|
||||
@@ -335,7 +340,8 @@ public class ImporterPlugin extends Plugin
|
||||
importAction = new DockingAction(title, this.getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
doSingleImportAction(getFolderFromContext(context));
|
||||
DomainFolder folder = getFolderFromContext(context);
|
||||
doSingleImportAction(folder);
|
||||
}
|
||||
};
|
||||
importAction.setMenuBarData(new MenuData(new String[] { "&File", title + "..." }, null,
|
||||
@@ -352,8 +358,9 @@ public class ImporterPlugin extends Plugin
|
||||
batchImportAction = new DockingAction(title, this.getName()) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
BatchImportDialog.showAndImport(tool, null, null, getFolderFromContext(context),
|
||||
getTool().getService(ProgramManager.class));
|
||||
DomainFolder folder = getFolderFromContext(context);
|
||||
ProgramManager programManager = getTool().getService(ProgramManager.class);
|
||||
BatchImportDialog.showAndImport(tool, null, null, folder, programManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -376,15 +383,15 @@ public class ImporterPlugin extends Plugin
|
||||
public void actionPerformed(ActionContext context) {
|
||||
if (context instanceof ListingActionContext) {
|
||||
ListingActionContext lac = (ListingActionContext) context;
|
||||
doImportSelectionAction(lac.getProgram(), lac.getSelection());
|
||||
doImportSelection(lac.getProgram(), lac.getSelection());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidContext(ActionContext context) {
|
||||
if (context instanceof ListingActionContext) {
|
||||
ProgramSelection selection =
|
||||
((ListingActionContext) context).getNavigatable().getSelection();
|
||||
Navigatable navigatable = ((ListingActionContext) context).getNavigatable();
|
||||
ProgramSelection selection = navigatable.getSelection();
|
||||
return selection != null && selection.getNumAddressRanges() == 1;
|
||||
}
|
||||
return false;
|
||||
@@ -445,13 +452,13 @@ public class ImporterPlugin extends Plugin
|
||||
private static DomainFolder getFolderFromContext(ActionContext context) {
|
||||
DomainFolder folder = null;
|
||||
Object contextObj = context.getContextObject();
|
||||
if (contextObj instanceof GTreeNode dataTreeNode) {
|
||||
if (contextObj instanceof DomainFolderNode dataTreeNode) {
|
||||
folder = DataTree.getRealInternalFolderForNode(dataTreeNode);
|
||||
}
|
||||
if (folder != null && folder.isInWritableProject()) {
|
||||
return folder;
|
||||
}
|
||||
return AppInfo.getActiveProject().getProjectData().getRootFolder();
|
||||
return null;
|
||||
}
|
||||
|
||||
private void initializeChooser(String title, String buttonText, boolean multiSelect) {
|
||||
@@ -528,7 +535,7 @@ public class ImporterPlugin extends Plugin
|
||||
});
|
||||
}
|
||||
|
||||
protected void doImportSelectionAction(Program program, ProgramSelection selection) {
|
||||
protected void doImportSelection(Program program, ProgramSelection selection) {
|
||||
if (selection == null || selection.getNumAddressRanges() != 1) {
|
||||
return;
|
||||
}
|
||||
@@ -545,23 +552,24 @@ public class ImporterPlugin extends Plugin
|
||||
|
||||
// create a tmp ByteProvider that contains the bytes from the selected region
|
||||
FileCacheEntry tmpFile;
|
||||
Address minAddress = range.getMinAddress();
|
||||
try (FileCacheEntryBuilder tmpFileBuilder =
|
||||
fsService().createTempFile(range.getLength())) {
|
||||
byte[] bytes = new byte[(int) range.getLength()];
|
||||
memory.getBytes(range.getMinAddress(), bytes);
|
||||
memory.getBytes(minAddress, bytes);
|
||||
tmpFileBuilder.write(bytes);
|
||||
tmpFile = tmpFileBuilder.finish();
|
||||
}
|
||||
|
||||
MemoryBlock block = memory.getBlock(range.getMinAddress());
|
||||
String rangeName =
|
||||
block.getName() + "[" + range.getMinAddress() + "," + range.getMaxAddress() + "]";
|
||||
ByteProvider bp =
|
||||
fsService().getNamedTempFile(tmpFile, program.getName() + " " + rangeName);
|
||||
MemoryBlock block = memory.getBlock(minAddress);
|
||||
Address maxAddress = range.getMaxAddress();
|
||||
String rangeName = block.getName() + "[" + minAddress + "," + maxAddress + "]";
|
||||
String tempName = program.getName() + " " + rangeName;
|
||||
ByteProvider bp = fsService().getNamedTempFile(tmpFile, tempName);
|
||||
LoaderMap loaderMap = LoaderService.getAllSupportedLoadSpecs(bp);
|
||||
|
||||
ImporterDialog importerDialog = new ImporterDialog(tool,
|
||||
tool.getService(ProgramManager.class), loaderMap, bp, null);
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
ImporterDialog importerDialog = new ImporterDialog(tool, pm, loaderMap, bp, null);
|
||||
tool.showDialog(importerDialog);
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
||||
@@ -77,7 +77,7 @@ public class BatchImportDialog extends DialogComponentProvider {
|
||||
public static void showAndImport(PluginTool tool, BatchInfo batchInfo, List<FSRL> initialFiles,
|
||||
DomainFolder defaultFolder, ProgramManager programManager) {
|
||||
BatchImportDialog dialog = new BatchImportDialog(batchInfo, defaultFolder, programManager);
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
if (initialFiles != null && !initialFiles.isEmpty()) {
|
||||
dialog.addSources(initialFiles);
|
||||
}
|
||||
@@ -231,7 +231,7 @@ public class BatchImportDialog extends DialogComponentProvider {
|
||||
// NOTE: using invokeLater to avoid event handling issues where
|
||||
// the spinner model gets updated several times (ie. multi-decrement when
|
||||
// it should be just 1 dec) if we do anything modal.
|
||||
SystemUtilities.runSwingLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
setMaxDepth(spinnerNumberModel.getNumber().intValue());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user