diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index b370d12021..8900480a7b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -39,9 +39,9 @@ import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.BreadthFirstIterator; import generic.jar.ResourceFile; -import generic.util.Path; import ghidra.app.plugin.core.script.osgi.*; import ghidra.app.script.*; +import ghidra.app.script.osgi.BundleHost; import ghidra.app.services.ConsoleService; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.ComponentProviderAdapter; @@ -97,9 +97,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } }; - GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) { + GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) { super(plugin.getTool(), "Script Manager", plugin.getName()); this.plugin = plugin; + this.bundleHost=bundleHost; setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setIcon(ResourceManager.loadImage("images/play.png")); @@ -161,8 +162,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { void renameScript() { ResourceFile script = getSelectedScript(); ResourceFile directory = script.getParentFile(); - Path path = GhidraScriptUtil.getScriptPath(directory); - if (path == null || path.isReadOnly()) { + if (!bundleStatusProvider.getModel().isWriteable(directory)) { Msg.showWarn(getClass(), getComponent(), getName(), "Unable to rename scripts in '" + directory + "'."); return; @@ -297,8 +297,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } ResourceFile directory = script.getParentFile(); - Path path = GhidraScriptUtil.getScriptPath(directory); - if (path == null || path.isReadOnly()) { + if (!bundleStatusProvider.getModel().isWriteable(directory)) { Msg.showWarn(getClass(), getComponent(), getName(), "Unable to delete scripts in '" + directory + "'."); return; @@ -339,9 +338,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } - public List getScriptDirectories() { + public List getScriptDirectories() { return bundleStatusProvider.getModel().getPaths().stream().filter( - BundlePath::isDirectory).collect(Collectors.toList()); + ResourceFile::isDirectory).collect(Collectors.toList()); } public void enableScriptDirectory(ResourceFile scriptDir) { @@ -396,9 +395,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } void runScript(String scriptName, TaskListener listener) { - List dirPaths = bundleStatusProvider.getModel().getPaths(); - for (Path dir : dirPaths) { - ResourceFile scriptSource = new ResourceFile(dir.getPath(), scriptName); + List dirPaths = bundleStatusProvider.getModel().getPaths(); + for (ResourceFile dir : dirPaths) { + ResourceFile scriptSource = new ResourceFile(dir, scriptName); if (scriptSource.exists()) { runScript(scriptSource, listener); return; @@ -536,11 +535,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { private void updateAvailableScriptFilesForAllPaths() { List scriptsToRemove = tableModel.getScripts(); List scriptAccumulator = new ArrayList<>(); - List bundlePaths = bundleStatusProvider.getModel().getPaths(); - for (BundlePath bundlePath : bundlePaths) { + List bundlePaths = bundleStatusProvider.getModel().getPaths(); + for (ResourceFile bundlePath : bundlePaths) { if (bundlePath.isDirectory()) { updateAvailableScriptFilesForDirectory(scriptsToRemove, scriptAccumulator, - bundlePath.getPath()); + bundlePath); } } @@ -731,8 +730,33 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return true; } - private void build() { + final private BundleHost bundleHost; + void startActivateDeactiveTask(BundlePath path, boolean activate) { + path.setBusy(true); + bundleStatusProvider.notifyTableChanged(); + + new TaskLauncher(new Task((activate ? "Activating" : "Deactivating ") + " bundle...") { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + try { + bundleHost.setActive(path, activate); + path.setActive(activate); + } + catch (Exception e) { + Msg.showError(this, GhidraScriptComponentProvider.this.getComponent(), + "activation failed", e); + } + finally { + path.setBusy(false); + bundleStatusProvider.notifyTableChanged(); + } + } + }, null, 1000); + + } + + private void build() { bundleStatusProvider = new BundleStatusProvider(plugin.getTool(), plugin.getName()); bundleStatusProvider.addListener(new BundlePathManagerListener() { @@ -744,8 +768,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { @Override public void bundleEnablementChanged(BundlePath path, boolean enabled) { - System.err.printf("XXXX %s is now %s\n", path.toString(), - enabled ? "enabled" : "disabled"); + if (path.isActive()) { + startActivateDeactiveTask(path, false); + } + if (path.isDirectory()) { performRefresh(); } @@ -753,8 +779,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { @Override public void bundleActivationChanged(BundlePath path, boolean newValue) { - System.err.printf("XXXX %s is now %s\n", path.toString(), - newValue ? "active" : "inactive"); + startActivateDeactiveTask(path, newValue); } }); @@ -1026,7 +1051,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { bundleStatusProvider.restoreState(saveState); // pull in the just-loaded paths - List paths = bundleStatusProvider.getModel().getPaths(); + List paths = bundleStatusProvider.getModel().getPaths(); GhidraScriptUtil.setScriptBundlePaths(paths); actionManager.restoreUserDefinedKeybindings(saveState); actionManager.restoreScriptsThatAreInTool(saveState); @@ -1162,4 +1187,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } + public List getWritableScriptDirectories() { + BundleStatusModel m = bundleStatusProvider.getModel(); + return m.getPaths().stream().filter(ResourceFile::isDirectory).filter( + m::isWriteable).collect(Collectors.toList()); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java index 498772040a..1acc3f3c63 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java @@ -26,6 +26,7 @@ import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.eclipse.EclipseConnection; import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin; import ghidra.app.script.GhidraState; +import ghidra.app.script.osgi.BundleHost; import ghidra.app.services.*; import ghidra.framework.options.SaveState; import ghidra.framework.options.ToolOptions; @@ -48,12 +49,15 @@ import ghidra.util.task.TaskListener; //@formatter:on public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService { - private GhidraScriptComponentProvider provider; + final private GhidraScriptComponentProvider provider; + + final private BundleHost bundleHost; public GhidraScriptMgrPlugin(PluginTool tool) { super(tool, true, true, true); - provider = new GhidraScriptComponentProvider(this); + bundleHost = BundleHost.getInstance(); + provider = new GhidraScriptComponentProvider(this, bundleHost); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java index d7683d80a7..aa6c5ccac3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java @@ -19,7 +19,8 @@ import java.awt.BorderLayout; import java.awt.Component; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import javax.swing.*; import javax.swing.event.ListSelectionEvent; @@ -31,7 +32,6 @@ import docking.widgets.MultiLineLabel; import docking.widgets.label.GLabel; import docking.widgets.list.ListPanel; import generic.jar.ResourceFile; -import generic.util.Path; import ghidra.app.script.*; import ghidra.util.HelpLocation; @@ -49,7 +49,7 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection public SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile, HelpLocation help) { - this(parent, title, componentProvider, getScriptPaths(), scriptFile, help); + this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(), scriptFile, help); } public SaveDialog(Component parent, String title, @@ -85,21 +85,6 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection DockingWindowManager.showDialog(parent, this); } - private static List getScriptPaths() { - List newPaths = new ArrayList<>(); - - List scriptPaths = GhidraScriptUtil.getScriptSourceDirectories(); - for (ResourceFile directory : scriptPaths) { - Path path = GhidraScriptUtil.getScriptPath(directory); - if (path != null && !path.isReadOnly()) { - newPaths.add(directory); - } - } - - Collections.sort(newPaths); - return newPaths; - } - private JPanel buildNamePanel() { nameField = new JTextField(20); nameField.setText(scriptFile == null ? "" : scriptFile.getName()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundlePath.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundlePath.java index f40b467716..7cb2a2ce66 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundlePath.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundlePath.java @@ -21,7 +21,10 @@ import generic.jar.ResourceFile; import generic.util.Path; public class BundlePath extends Path { + final Type type; + boolean active = false; + boolean busy = false; public static enum Type { BndScript, Jar, SourceDir, INVALID @@ -55,47 +58,43 @@ public class BundlePath extends Path { return Type.INVALID; } - final Type type; - - public BundlePath(File path) { - super(path); - type = getType(getPath()); - } - public Type getType() { return type; } - public BundlePath(ResourceFile rf) { - super(rf); + BundlePath(String path, boolean enabled, boolean readonly) { + super(path, enabled, false /*editable */, readonly); type = getType(getPath()); } - public BundlePath(String absolutePath) { - super(absolutePath); + BundlePath(ResourceFile path, boolean enabled, boolean readonly) { + super(path, enabled, false /* editable */, readonly); type = getType(getPath()); } - public BundlePath(String a, boolean b, boolean c, boolean d) { - super(a, b, c, d); - type = getType(getPath()); - } - - public BundlePath(ResourceFile a, boolean b, boolean c, boolean d) { - super(a, b, c, d); - type = getType(getPath()); - } - - public boolean isActive() { - return active; - } - - public void setActive(Boolean b) { - active = b; - } - public boolean isDirectory() { return getPath().isDirectory(); } + public boolean isActive() { + return active; + } + + @Override + public boolean isEditable() { + return false; + } + + public void setActive(boolean b) { + active = b; + } + + public void setBusy(boolean b) { + busy = b; + } + + public boolean getBusy() { + return busy; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusModel.java index 7f30594ad8..ae182722a4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusModel.java @@ -16,8 +16,8 @@ */ package ghidra.app.plugin.core.script.osgi; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import docking.widgets.table.AbstractSortedTableModel; import generic.jar.ResourceFile; @@ -41,20 +41,25 @@ public class BundleStatusModel extends AbstractSortedTableModel { } boolean editable(BundlePath path) { - return true; + return false; } Object getValue(BundlePath path) { - return path; + return null; } void setValue(BundlePath path, Object aValue) { - // do nothing + throw new RuntimeException(name + " is not editable!"); } } Column enabledColumn = new Column("Enabled", Boolean.class) { + @Override + boolean editable(BundlePath path) { + return path.exists(); + } + @Override Object getValue(BundlePath path) { return path.isEnabled(); @@ -67,6 +72,11 @@ public class BundleStatusModel extends AbstractSortedTableModel { } }; Column activeColumn = new Column("Active", Boolean.class) { + @Override + boolean editable(BundlePath path) { + return path.exists(); // XXX maybe only if it's already enabled + } + @Override Object getValue(BundlePath path) { return path.isActive(); @@ -93,15 +103,12 @@ public class BundleStatusModel extends AbstractSortedTableModel { Column pathColumn = new Column("Path", BundlePath.class) { @Override boolean editable(BundlePath path) { - return true; + return false; } @Override - void setValue(BundlePath path, Object aValue) { - if (path.isEditable()) { - BundlePath newpath = (BundlePath) aValue; - path.setPath(newpath.getPath()); - } + Object getValue(BundlePath path) { + return path; } }; Column badColumn = new Column("INVALID", Object.class); @@ -118,26 +125,21 @@ public class BundleStatusModel extends AbstractSortedTableModel { } private BundleStatusProvider provider; - private List paths = new ArrayList<>(); + private List paths; BundleStatusModel(BundleStatusProvider provider) { super(); this.provider = provider; - this.paths.addAll(dedupPaths(GhidraScriptUtil.getDefaultScriptBundles())); + + // add unmodifiable paths + this.paths = GhidraScriptUtil.getSystemScriptPaths().stream().distinct().map( + f -> new BundlePath(f, true, true)).collect(Collectors.toList()); + // add user path + this.paths.add(0, new BundlePath(GhidraScriptUtil.getUserScriptDirectory(), true, false)); fireTableDataChanged(); } - private List dedupPaths(List newPaths) { - List dedupedPaths = new ArrayList<>(); - for (BundlePath path : newPaths) { - if (!dedupedPaths.contains(path)) { - dedupedPaths.add(path); - } - } - return dedupedPaths; - } - void clear() { paths.clear(); } @@ -146,21 +148,16 @@ public class BundleStatusModel extends AbstractSortedTableModel { return new ArrayList(paths); } - public List getPaths() { - List list = new ArrayList<>(); + public List getPaths() { + List list = new ArrayList<>(); for (BundlePath path : paths) { if (path.isEnabled()) { - list.add(path); + list.add(path.getPath()); } } return list; } - public void setPaths(List paths) { - this.paths = new ArrayList<>(paths); - fireTableDataChanged(); - } - void addPath(BundlePath path) { if (paths.contains(path)) { return; @@ -176,7 +173,7 @@ public class BundleStatusModel extends AbstractSortedTableModel { list.add(paths.get(selectedRow)); } for (BundlePath path : list) { - if (path.isEditable()) { + if (!path.isReadOnly()) { paths.remove(path); } else { @@ -259,11 +256,48 @@ public class BundleStatusModel extends AbstractSortedTableModel { return false; } } - BundlePath p = new BundlePath(dir); - p.setEnabled(true); + BundlePath p = new BundlePath(dir, true, false); addPath(p); - Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle, dir.getAbsolutePath()); + Preferences.setProperty(BundleStatusProvider.preferenceForLastSelectedBundle, + dir.getAbsolutePath()); provider.fireBundlesChanged(); return true; } + + /** + * Test whether the given bundle is managed and not marked readonly + * @param bundle the path to test + * @return true if the bundle is managed and not marked readonly + */ + public boolean isWriteable(ResourceFile bundle) { + Optional o = paths.stream().filter( + bp -> bp.isDirectory() && bp.getPath().equals(bundle)).findFirst(); + return o.isPresent() && !o.get().isReadOnly(); + } + + /** + * This is for testing only! + * + * each path is marked editable and non-readonly + * + * @param testingPaths the paths to use + */ + public void setPathsForTesting(List testingPaths) { + this.paths = testingPaths.stream().map(f -> new BundlePath(f, true, false)).collect( + Collectors.toList()); + fireTableDataChanged(); + } + + /** + * This is for testing only! + * + * insert path, marked editable and non-readonly + * @param index index to insert at + * @param path the path to insert + */ + public void insertPathForTesting(int index, String path) { + paths.add(0, new BundlePath(path, true, false)); + fireTableRowsInserted(0, 0); + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusProvider.java index 8d7c897428..600a4e9467 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/osgi/BundleStatusProvider.java @@ -22,11 +22,13 @@ import java.util.Arrays; import java.util.List; import javax.swing.*; +import javax.swing.event.TableModelEvent; import javax.swing.table.TableColumn; import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooserMode; import docking.widgets.table.*; +import generic.jar.ResourceFile; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; @@ -51,6 +53,10 @@ public class BundleStatusProvider extends ComponentProviderAdapter { private GhidraFileFilter filter; private ArrayList listeners = new ArrayList<>(); + public void notifyTableChanged() { + bundlePathTable.notifyTableChanged(new TableModelEvent(bundleStatusModel)); + } + void fireBundlesChanged() { for (BundlePathManagerListener listener : listeners) { listener.bundlesChanged(); @@ -108,7 +114,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { panel = new JPanel(new BorderLayout(5, 5)); selectionColor = new Color(204, 204, 255); - + addButton = new JButton(ResourceManager.loadImage("images/Plus.png")); addButton.setName("AddBundle"); addButton.setToolTipText("Display file chooser to add bundles to list"); @@ -138,6 +144,9 @@ public class BundleStatusProvider extends ComponentProviderAdapter { bundlePathTable.setSelectionForeground(Color.BLACK); bundlePathTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + // to allow custom cell renderers + bundlePathTable.setAutoCreateColumnsFromModel(false); + int skinnyWidth = 50; TableColumn column = @@ -152,6 +161,27 @@ public class BundleStatusProvider extends ComponentProviderAdapter { column.setMinWidth(skinnyWidth); column.setMaxWidth(skinnyWidth); column.setWidth(skinnyWidth); + column.setCellRenderer(new GBooleanCellRenderer() { + @Override + public Component getTableCellRendererComponent(GTableCellRenderingData data) { + BundlePath path = (BundlePath) data.getRowObject(); + Component x = super.getTableCellRendererComponent(data); + if (path.getBusy()) { + cb.setVisible(false); + cb.setEnabled(false); + setHorizontalAlignment(SwingConstants.CENTER); + setText("..."); + } + else { + cb.setVisible(true); + cb.setEnabled(true); + setText(""); + } + return x; + + } + + }); column = bundlePathTable.getColumnModel().getColumn(bundleStatusModel.typeColumn.index); @@ -163,13 +193,13 @@ public class BundleStatusProvider extends ComponentProviderAdapter { column.setCellRenderer(new GTableCellRenderer() { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { - JLabel renderer = (JLabel) super.getTableCellRendererComponent(data); + JLabel c = (JLabel) super.getTableCellRendererComponent(data); BundlePath path = (BundlePath) data.getValue(); if (!path.exists()) { - renderer.setForeground(Color.RED); + c.setForeground(Color.RED); } - return renderer; + return c; } }); @@ -262,7 +292,7 @@ public class BundleStatusProvider extends ComponentProviderAdapter { Preferences.setProperty(preferenceForLastSelectedBundle, files.get(0).getAbsolutePath()); for (File element : files) { - BundlePath p = new BundlePath(element); + BundlePath p = new BundlePath(new ResourceFile(element), true, false); bundleStatusModel.addPath(p); } fireBundlesChanged(); @@ -283,22 +313,19 @@ public class BundleStatusProvider extends ComponentProviderAdapter { String[] pathArr = new String[paths.size()]; boolean[] enableArr = new boolean[paths.size()]; - boolean[] editArr = new boolean[paths.size()]; boolean[] readArr = new boolean[paths.size()]; int index = 0; for (BundlePath path : paths) { pathArr[index] = path.getPathAsString(); enableArr[index] = path.isEnabled(); - editArr[index] = path.isEditable(); readArr[index] = path.isReadOnly(); ++index; } - ss.putStrings("BundleManagerPanel_PATH", pathArr); - ss.putBooleans("BundleManagerPanel_ENABLE", enableArr); - ss.putBooleans("BundleManagerPanel_EDIT", editArr); - ss.putBooleans("BundleManagerPanel_READ", readArr); + ss.putStrings("BundleStatus_PATH", pathArr); + ss.putBooleans("BundleStatus_ENABLE", enableArr); + ss.putBooleans("BundleStatus_READ", readArr); } /** @@ -306,22 +333,21 @@ public class BundleStatusProvider extends ComponentProviderAdapter { * @param ss the SaveState object */ public void restoreState(SaveState ss) { - String[] pathArr = ss.getStrings("BundleManagerPanel_PATH", new String[0]); + String[] pathArr = ss.getStrings("BundleStatus_PATH", new String[0]); if (pathArr.length == 0) { return; } boolean[] enableArr = - ss.getBooleans("BundleManagerPanel_ENABLE", new boolean[pathArr.length]); - boolean[] editArr = ss.getBooleans("BundleManagerPanel_EDIT", new boolean[pathArr.length]); - boolean[] readArr = ss.getBooleans("BundleManagerPanel_READ", new boolean[pathArr.length]); + ss.getBooleans("BundleStatus_ENABLE", new boolean[pathArr.length]); + boolean[] readArr = ss.getBooleans("BundleStatus_READ", new boolean[pathArr.length]); List oldPaths = bundleStatusModel.getAllPaths(); bundleStatusModel.clear(); for (int i = 0; i < pathArr.length; i++) { - BundlePath path = new BundlePath(pathArr[i], enableArr[i], editArr[i], readArr[i]); + BundlePath path = new BundlePath(pathArr[i], enableArr[i], readArr[i]); BundlePath oldPath = getPath(path.getPathAsString(), oldPaths); if (oldPath != null) { if (!oldPath.isEditable()) { @@ -361,7 +387,4 @@ public class BundleStatusProvider extends ComponentProviderAdapter { bundlePathTable.dispose(); } - void selectRow(int rowIndex) { - bundlePathTable.selectRow(rowIndex); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index bcc149f420..01e3f92211 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -24,7 +24,6 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.map.LazyMap; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.framework.Application; import ghidra.program.model.listing.Program; import ghidra.util.Msg; @@ -48,7 +47,7 @@ public class GhidraScriptUtil { */ public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory(); - private static List scriptBundlePaths = new ArrayList<>(); + private static List scriptBundlePaths = new ArrayList<>(); private static Map scriptFileToInfoMap = new HashMap<>(); @@ -56,7 +55,8 @@ public class GhidraScriptUtil { LazyMap.lazyMap(new HashMap>(), () -> new ArrayList<>()); static { - scriptBundlePaths = getDefaultScriptBundles(); + scriptBundlePaths = getSystemScriptPaths(); + scriptBundlePaths.add(0, getUserScriptDirectory()); } /** @@ -81,25 +81,22 @@ public class GhidraScriptUtil { * Returns a list of the default script directories. * @return a list of the default script directories */ - public static List getDefaultScriptBundles() { - - List pathsList = new ArrayList<>(); + public static List getSystemScriptPaths() { + List pathsList = new ArrayList<>(); addScriptPaths(pathsList, SCRIPTS_SUBDIR_NAME); addScriptPaths(pathsList, DEV_SCRIPTS_SUBDIR_NAME); Collections.sort(pathsList); - - // this one should always be first - pathsList.add(0, new BundlePath(new ResourceFile(USER_SCRIPTS_DIR), true, false, false)); return pathsList; } - private static void addScriptPaths(List pathsList, String directoryName) { - Iterable files = Application.findModuleSubDirectories(directoryName); - for (ResourceFile file : files) { - pathsList.add(new BundlePath(file, true, false, true)); - } + public static ResourceFile getUserScriptDirectory() { + return new ResourceFile(USER_SCRIPTS_DIR); + } + + private static void addScriptPaths(List pathsList, String directoryName) { + pathsList.addAll(Application.findModuleSubDirectories(directoryName)); } /** @@ -144,39 +141,18 @@ public class GhidraScriptUtil { * @return a list of the current script directories */ public static List getScriptSourceDirectories() { - ArrayList dirs = new ArrayList<>(); - for (BundlePath path : scriptBundlePaths) { - if (path.isDirectory()) { - dirs.add(path.getPath()); - } - } - return dirs; + return scriptBundlePaths.stream().filter(ResourceFile::isDirectory).collect( + Collectors.toList()); } /** * Sets the script bundle paths * @param newPaths the new script bundle paths */ - public static void setScriptBundlePaths(List newPaths) { + public static void setScriptBundlePaths(List newPaths) { scriptBundlePaths = new ArrayList<>(newPaths); } - /** - * Returns the PATH for the specified directory. - * @param directory the directory - * @return the path for the specified directory - */ - public static BundlePath getScriptPath(ResourceFile directory) { - if (directory.isDirectory()) { - for (BundlePath path : scriptBundlePaths) { - if (path.getPath().equals(directory)) { - return path; - } - } - } - return null; - } - public static Path getOsgiDir() { Path usersettings = Application.getUserSettingsDirectory().toPath(); return usersettings.resolve("osgi"); @@ -218,7 +194,7 @@ public class GhidraScriptUtil { } Set dirs = new HashSet<>(); - for (BundlePath path : scriptBundlePaths) { + for (ResourceFile scriptDir : scriptBundlePaths) { // // Assumed structure of script dir path: // /some/path/Ghidra/Features/Module/ghidra_scripts @@ -226,7 +202,6 @@ public class GhidraScriptUtil { // Desired path: // /some/path/Ghidra/Features/Module/bin/scripts - ResourceFile scriptDir = path.getPath(); ResourceFile moduleDir = scriptDir.getParentFile(); dirs.add(new ResourceFile(moduleDir, BIN_DIR_NAME + File.separator + "scripts")); } @@ -381,14 +356,14 @@ public class GhidraScriptUtil { * @throws IOException if an i/o error occurs */ public static ResourceFile createNewScript(GhidraScriptProvider provider, - ResourceFile parentDirectory, List scriptDirectories) throws IOException { + ResourceFile parentDirectory, List scriptDirectories) throws IOException { String baseName = GhidraScriptConstants.DEFAULT_SCRIPT_NAME; String extension = provider.getExtension(); return createNewScript(baseName, extension, parentDirectory, scriptDirectories); } private static ResourceFile createNewScript(String scriptName, String extension, - ResourceFile parentDirctory, List scriptDirectories) throws IOException { + ResourceFile parentDirctory, List scriptDirectories) throws IOException { String baseName = scriptName; String className = baseName + extension; @@ -409,13 +384,12 @@ public class GhidraScriptUtil { } /** Returns true if the given filename exists in any of the given directories */ - private static ResourceFile findScriptFileInPaths(List scriptDirectories, + private static ResourceFile findScriptFileInPaths(List scriptDirectories, String filename) { String validatedName = fixupName(filename); - for (BundlePath path : scriptDirectories) { - ResourceFile resourceFile = path.getPath(); + for (ResourceFile resourceFile : scriptDirectories) { if (resourceFile.isDirectory()) { ResourceFile file = new ResourceFile(resourceFile, validatedName); if (file.exists()) { @@ -477,8 +451,8 @@ public class GhidraScriptUtil { public static List getAllScripts() { List scriptList = new ArrayList<>(); - for (BundlePath dirPath : scriptBundlePaths) { - updateAvailableScriptFilesForDirectory(scriptList, dirPath.getPath()); + for (ResourceFile dirPath : scriptBundlePaths) { + updateAvailableScriptFilesForDirectory(scriptList, dirPath); } return scriptList; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index 7817ae2802..873a9520ce 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -26,7 +26,7 @@ import ghidra.app.script.osgi.*; import ghidra.util.Msg; public class JavaScriptProvider extends GhidraScriptProvider { - public SourceBundleInfo getBundleInfoForSource(ResourceFile sourceFile) { + static public SourceBundleInfo getBundleInfoForSource(ResourceFile sourceFile) { ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile); if (sourceDir == null) { return null; @@ -87,7 +87,7 @@ public class JavaScriptProvider extends GhidraScriptProvider { } } - public Class loadClass(ResourceFile sourceFile, PrintWriter writer) + static public Class loadClass(ResourceFile sourceFile, PrintWriter writer) throws IOException, OSGiException, ClassNotFoundException, InterruptedException { if (writer == null) { @@ -95,72 +95,7 @@ public class JavaScriptProvider extends GhidraScriptProvider { } SourceBundleInfo bi = getBundleInfoForSource(sourceFile); - - bi.updateFromFilesystem(writer); - - // needsCompile => needsBundleActivate - boolean needsCompile = false; - boolean needsBundleActivate = false; - - int failing = bi.getFailingSourcesCount(); - int newSourcecount = bi.getNewSourcesCount(); - - long lastBundleActivation = 0; // XXX record last bundle activation in pathmanager - if (failing > 0 && (lastBundleActivation > bi.getLastCompileAttempt())) { - needsCompile = true; - } - - if (newSourcecount == 0) { - if (failing > 0) { - writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n", - bi.getSourceDir().toString(), failing, failing > 1 ? "s" : ""); - writer.printf("%s\n", bi.getPreviousBuildErrors()); - } - if (bi.newManifestFile()) { - needsCompile = true; - } - } - else { - needsCompile = true; - } - - needsBundleActivate |= needsCompile; - - BundleHost bundle_host = BundleHost.getInstance(); - if (needsBundleActivate) { - writer.printf("%s has %d new/updated %d failed in previous build(s)%s\n", - bi.getSourceDir().toString(), newSourcecount, failing, - bi.newManifestFile() ? " and the manifest is new" : ""); - - // if there a bundle is currently active, uninstall it - Bundle b = bi.getBundle(); - if (b != null) { - bundle_host.synchronousUninstall(b); - } - - // once we've committed to recompile and regenerate generated classes, delete the old stuff - if (needsCompile) { - bi.deleteOldBinaries(); - - BundleCompiler bc = new BundleCompiler(bundle_host); - - long startTime = System.nanoTime(); - bc.compileToExplodedBundle(bi, writer); - long endTime = System.nanoTime(); - writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9); - } - } - // as much source as possible built, install bundle and start it if necessary - Bundle b = bi.getBundle(); - if (b == null) { - b = bi.install(); - needsBundleActivate = true; - } - - if (needsBundleActivate) { - bundle_host.synchronousStart(b); - } - + Bundle b = BundleHost.getInstance().activate(bi, writer); String classname = bi.classNameForScript(sourceFile); Class clazz = b.loadClass(classname); // throws ClassNotFoundException return clazz; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/osgi/BundleHost.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/osgi/BundleHost.java index 7c1b9ea703..aafbb1c693 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/osgi/BundleHost.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/osgi/BundleHost.java @@ -36,6 +36,7 @@ import org.osgi.framework.wiring.*; import org.osgi.service.log.*; import generic.jar.ResourceFile; +import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.script.GhidraScriptUtil; import ghidra.util.exception.CancelledException; import ghidra.util.task.*; @@ -532,4 +533,101 @@ public class BundleHost { } } + /** + * Change the status of the bundle referenced by path + * + * @param path the bundle path to activate/deactivate + * @param value the new activation value + * @throws InterruptedException + */ + public void setActive(BundlePath path, boolean value) throws InterruptedException { + if (path.isDirectory()) { + SourceBundleInfo sbi = getSourceBundleInfo(path.getPath()); + } + Thread.sleep(3000); + } + + /** + * synchronously perform steps necessary to activate bundle: + *
    + *
  1. if source bundle, checkCompile
  2. + *
  3. b
  4. + * + *
+ * + * @param bi the bundle info + * @param writer where to write issues + * @throws OSGiException if bundle operations fail + * @throws IOException if there are issues with the contents of the bundle + * @return the activated bundle + * @throws InterruptedException if interrupted while waiting for bundle state change + */ + public Bundle activate(SourceBundleInfo bi, PrintWriter writer) + throws OSGiException, IOException, InterruptedException { + bi.updateFromFilesystem(writer); + + // needsCompile => needsBundleActivate + boolean needsCompile = false; + boolean needsBundleActivate = false; + + int failing = bi.getFailingSourcesCount(); + int newSourcecount = bi.getNewSourcesCount(); + + long lastBundleActivation = 0; // XXX record last bundle activation in bundlestatusmodel + if (failing > 0 && (lastBundleActivation > bi.getLastCompileAttempt())) { + needsCompile = true; + } + + if (newSourcecount == 0) { + if (failing > 0) { + writer.printf("%s hasn't changed, with %d file%s failing in previous build(s):\n", + bi.getSourceDir().toString(), failing, failing > 1 ? "s" : ""); + writer.printf("%s\n", bi.getPreviousBuildErrors()); + } + if (bi.newManifestFile()) { + needsCompile = true; + } + } + else { + needsCompile = true; + } + + needsBundleActivate |= needsCompile; + + if (needsBundleActivate) { + writer.printf("%s has %d new/updated %d failed in previous build(s)%s\n", + bi.getSourceDir().toString(), newSourcecount, failing, + bi.newManifestFile() ? " and the manifest is new" : ""); + + // if there a bundle is currently active, uninstall it + Bundle b = bi.getBundle(); + if (b != null) { + synchronousUninstall(b); + } + + // once we've committed to recompile and regenerate generated classes, delete the old stuff + if (needsCompile) { + bi.deleteOldBinaries(); + + BundleCompiler bc = new BundleCompiler(this); + + long startTime = System.nanoTime(); + bc.compileToExplodedBundle(bi, writer); + long endTime = System.nanoTime(); + writer.printf("%3.2f seconds compile time.\n", (endTime - startTime) / 1e9); + } + } + // as much source as possible built, install bundle and start it if necessary + Bundle b = bi.getBundle(); + if (b == null) { + b = bi.install(); + needsBundleActivate = true; + } + + if (needsBundleActivate) { + synchronousStart(b); + } + return b; + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java index 434bab8a5c..2970418564 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/GhidraScriptRunner.java @@ -22,7 +22,6 @@ import java.util.List; import generic.jar.ResourceFile; import ghidra.GhidraApplicationLayout; import ghidra.GhidraLaunchable; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.script.*; import ghidra.framework.Application; import ghidra.framework.HeadlessGhidraApplicationConfiguration; @@ -31,7 +30,7 @@ import ghidra.program.database.ProgramDB; import ghidra.program.model.listing.Program; import ghidra.util.Msg; import ghidra.util.SystemUtilities; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; import utility.application.ApplicationLayout; /** @@ -76,7 +75,7 @@ public class GhidraScriptRunner implements GhidraLaunchable { try { PrintWriter writer = new PrintWriter(System.out); Msg.info(this, "SCRIPT: " + scriptName); - script.execute(scriptState, TaskMonitorAdapter.DUMMY_MONITOR, writer); + script.execute(scriptState, TaskMonitor.DUMMY, writer); writer.flush(); } catch (Exception exc) { @@ -100,8 +99,8 @@ public class GhidraScriptRunner implements GhidraLaunchable { GhidraScriptProvider provider = GhidraScriptUtil.getProvider(scriptSourceFile); if (provider == null) { - throw new IOException("Missing plugin needed to run scripts of this type. Please " - + "ensure you have installed the necessary plugin."); + throw new IOException("Missing plugin needed to run scripts of this type. Please " + + "ensure you have installed the necessary plugin."); } PrintWriter writer = new PrintWriter(System.out); @@ -192,20 +191,18 @@ public class GhidraScriptRunner implements GhidraLaunchable { * Gather paths where scripts may be found. */ private void initializeScriptPaths() { - List paths; + List paths; if (scriptPaths == null || scriptPaths.isEmpty()) { - paths = GhidraScriptUtil.getDefaultScriptBundles(); + paths = GhidraScriptUtil.getSystemScriptPaths(); + paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } else { paths = new ArrayList<>(); for (String path : scriptPaths) { - paths.add(new BundlePath(path, true, false, true)); - } - for (BundlePath path : GhidraScriptUtil.getDefaultScriptBundles()) { - if (path.isEnabled() && !paths.contains(path)) { - paths.add(path); - } + paths.add(new ResourceFile(path)); } + paths.addAll(GhidraScriptUtil.getSystemScriptPaths()); + paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } GhidraScriptUtil.setScriptBundlePaths(paths); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java index 07c3ba7084..5c711bdbe8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/headless/HeadlessAnalyzer.java @@ -26,7 +26,6 @@ import generic.util.Path; import ghidra.GhidraApplicationLayout; import ghidra.GhidraJarApplicationLayout; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.script.*; import ghidra.app.util.headless.HeadlessScript.HeadlessContinuationOption; import ghidra.app.util.importer.AutoImporter; @@ -661,20 +660,18 @@ public class HeadlessAnalyzer { */ private void initializeScriptPaths() { - List paths; + List paths; if (options.scriptPaths == null || options.scriptPaths.isEmpty()) { - paths = GhidraScriptUtil.getDefaultScriptBundles(); + paths = GhidraScriptUtil.getSystemScriptPaths(); + paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } else { paths = new ArrayList<>(); for (String path : options.scriptPaths) { - paths.add(new BundlePath(path, true, false, true)); - } - for (BundlePath path : GhidraScriptUtil.getDefaultScriptBundles()) { - if (path.isEnabled() && !paths.contains(path)) { - paths.add(path); - } + paths.add(new ResourceFile(path)); } + paths.addAll(GhidraScriptUtil.getSystemScriptPaths()); + paths.add(0, GhidraScriptUtil.getUserScriptDirectory()); } GhidraScriptUtil.setScriptBundlePaths(paths); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 33a58f6fcc..b625eafb15 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -48,7 +48,6 @@ import generic.jar.ResourceFile; import generic.test.TestUtils; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.console.ConsoleComponentProvider; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.plugin.core.script.osgi.BundleStatusProvider; import ghidra.app.script.*; import ghidra.app.services.ConsoleService; @@ -979,10 +978,10 @@ public abstract class AbstractGhidraScriptMgrPluginTest // destroy any NewScriptxxx files...and Temp ones too BundleStatusProvider bundleStatusProvider = - (BundleStatusProvider) TestUtils.getInstanceField("bundlePathManager", provider); - List paths = bundleStatusProvider.getModel().getPaths(); - for (BundlePath path : paths) { - File file = path.getPath().getFile(false); + (BundleStatusProvider) TestUtils.getInstanceField("bundleStatusProvider", provider); + List paths = bundleStatusProvider.getModel().getPaths(); + for (ResourceFile path : paths) { + File file = path.getFile(false); File[] listFiles = file.listFiles(); if (listFiles == null) { continue; diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java index 85f1eb459e..c9c18f8578 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin3Test.java @@ -19,9 +19,7 @@ import static org.junit.Assert.*; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.io.File; import java.io.IOException; -import java.util.List; import javax.swing.*; @@ -32,7 +30,6 @@ import docking.action.DockingActionIf; import docking.widgets.filter.FilterTextField; import docking.widgets.list.ListPanel; import generic.jar.ResourceFile; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.plugin.core.script.osgi.BundleStatusProvider; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.JavaScriptProvider; @@ -286,13 +283,11 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes final BundleStatusProvider bundleStatusProvider = waitForComponentProvider(BundleStatusProvider.class); - final File dir = new File(getTestDirectoryPath() + "/test_scripts"); - dir.mkdirs(); + final ResourceFile dir = new ResourceFile(getTestDirectoryPath() + "/test_scripts"); + dir.getFile(false).mkdirs(); SwingUtilities.invokeLater(() -> { - List paths = bundleStatusProvider.getModel().getPaths(); - paths.add(0, new BundlePath(dir)); - bundleStatusProvider.getModel().setPaths(paths); + bundleStatusProvider.getModel().insertPathForTesting(0, dir.getAbsolutePath()); }); waitForSwing(); @@ -333,7 +328,7 @@ public class GhidraScriptMgrPlugin3Test extends AbstractGhidraScriptMgrPluginTes assertTrue(newScript.exists()); assertNotNull(newScript); - assertEquals(dir, newScript.getParentFile().getFile(false)); + assertEquals(dir.getAbsolutePath(), newScript.getParentFile().getFile(false).getAbsolutePath()); newScript.delete(); dir.delete(); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index 4daab0a1a6..413c64909b 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -28,7 +28,6 @@ import docking.widgets.tree.GTreeNode; import generic.jar.ResourceFile; import ghidra.app.plugin.core.console.ConsoleComponentProvider; import ghidra.app.plugin.core.script.*; -import ghidra.app.plugin.core.script.osgi.BundlePath; import ghidra.app.plugin.core.script.osgi.BundleStatusProvider; import ghidra.app.script.GhidraScriptUtil; import ghidra.app.services.ConsoleService; @@ -50,10 +49,10 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator performAction("New", "GhidraScriptMgrPlugin", false); - JDialog d = waitForJDialog(null, "New Script: Type", 5000); + JDialog d = waitForJDialog("New Script: Type"); pressButtonByText(d, "OK"); - d = waitForJDialog(null, "New Script", 5000); + d = waitForJDialog("New Script"); pressButtonByText(d, "OK"); captureIsolatedProvider(GhidraScriptEditorComponentProvider.class, 597, 600); @@ -111,13 +110,13 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator @Test public void testScript_Dirs() throws Exception { - List paths = new ArrayList<>(); - paths.add(new BundlePath("$USER_HOME/ghidra_scripts")); - paths.add(new BundlePath("$GHIDRA_HOME/Features/Base/ghidra_scripts")); - paths.add(new BundlePath("/User/defined/invalid/directory")); + List paths = new ArrayList<>(); + paths.add("$USER_HOME/ghidra_scripts"); + paths.add("$GHIDRA_HOME/Features/Base/ghidra_scripts"); + paths.add("/User/defined/invalid/directory"); BundleStatusProvider bundleStatusProvider = showProvider(BundleStatusProvider.class); - bundleStatusProvider.getModel().setPaths(paths); + bundleStatusProvider.getModel().setPathsForTesting(paths); waitForComponentProvider(BundleStatusProvider.class); captureComponent(bundleStatusProvider.getComponent());