mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-3697 Added delayed ProjectFileManager disposal in support of URL use
and opening linked project files and renamed ProjectFileData to DefaultProjectData.
This commit is contained in:
parent
5ef4b269a1
commit
3eb642885c
51 changed files with 1636 additions and 813 deletions
|
@ -23,7 +23,7 @@ import org.jdom.Element;
|
||||||
import ghidra.app.services.DebuggerTraceManagerService;
|
import ghidra.app.services.DebuggerTraceManagerService;
|
||||||
import ghidra.app.services.TraceRecorder;
|
import ghidra.app.services.TraceRecorder;
|
||||||
import ghidra.dbg.target.TargetObject;
|
import ghidra.dbg.target.TargetObject;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
@ -664,7 +664,7 @@ public class DebuggerCoordinates {
|
||||||
if (projData == null) {
|
if (projData == null) {
|
||||||
try {
|
try {
|
||||||
// FIXME! orphaned instance - transient in nature
|
// FIXME! orphaned instance - transient in nature
|
||||||
projData = new ProjectFileManager(projLoc, false, false);
|
projData = new DefaultProjectData(projLoc, false, false);
|
||||||
}
|
}
|
||||||
catch (NotOwnerException e) {
|
catch (NotOwnerException e) {
|
||||||
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
|
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
|
||||||
|
|
|
@ -25,6 +25,7 @@ import docking.widgets.tree.GTreeNode;
|
||||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||||
import ghidra.dbg.target.*;
|
import ghidra.dbg.target.*;
|
||||||
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||||
|
import ghidra.framework.model.DomainObject;
|
||||||
import ghidra.framework.model.DomainObjectClosedListener;
|
import ghidra.framework.model.DomainObjectClosedListener;
|
||||||
import ghidra.trace.model.*;
|
import ghidra.trace.model.*;
|
||||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||||
|
@ -45,7 +46,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void domainObjectClosed() {
|
public void domainObjectClosed(DomainObject dobj) {
|
||||||
setTrace(null);
|
setTrace(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void domainObjectClosed() {
|
public void domainObjectClosed(DomainObject dobj) {
|
||||||
|
// assume dobj == program
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,9 +354,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
|
||||||
* trace, or bogus external libraries in a mapped program, scoring libraries before module
|
* trace, or bogus external libraries in a mapped program, scoring libraries before module
|
||||||
* names should not cause problems.
|
* names should not cause problems.
|
||||||
*/
|
*/
|
||||||
Comparator<IndexEntry> comparator = byIsLibrary
|
Comparator<IndexEntry> comparator =
|
||||||
.thenComparing(byNameSource)
|
byIsLibrary.thenComparing(byNameSource).thenComparing(byFolderUses);
|
||||||
.thenComparing(byFolderUses);
|
|
||||||
return projectData.getFileByID(entries.stream().max(comparator).get().dfID);
|
return projectData.getFileByID(entries.stream().max(comparator).get().dfID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -14,16 +13,16 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.Project;
|
import ghidra.framework.model.Project;
|
||||||
import ghidra.framework.store.FileSystem;
|
import ghidra.framework.store.FileSystem;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
import ghidra.framework.store.local.LocalFolderItem;
|
import ghidra.framework.store.local.LocalFolderItem;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class CleanupMergeDatabasesScript extends GhidraScript {
|
public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,8 +30,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||||
|
|
||||||
Project project = state.getProject();
|
Project project = state.getProject();
|
||||||
|
|
||||||
ProjectFileManager fileMgr = (ProjectFileManager) project.getProjectData();
|
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||||
LocalFileSystem fs = (LocalFileSystem) fileMgr.getPrivateFileSystem();
|
LocalFileSystem fs = (LocalFileSystem) projectData.getPrivateFileSystem();
|
||||||
|
|
||||||
int cnt = cleanupFolder(fs, "/");
|
int cnt = cleanupFolder(fs, "/");
|
||||||
|
|
||||||
|
@ -61,9 +60,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fs.getItemNames(folderPath, true)
|
// fs.getItemNames(folderPath, true)
|
||||||
String[] itemNames =
|
String[] itemNames = (String[]) invokeInstanceMethod("getItemNames", fs,
|
||||||
(String[]) invokeInstanceMethod("getItemNames", fs, new Class[] { String.class,
|
new Class[] { String.class, boolean.class }, new Object[] { folderPath, true });
|
||||||
boolean.class }, new Object[] { folderPath, true });
|
|
||||||
|
|
||||||
for (String itemName : itemNames) {
|
for (String itemName : itemNames) {
|
||||||
if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) {
|
if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) {
|
||||||
|
@ -78,8 +76,9 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||||
else {
|
else {
|
||||||
// make sure we get item out of index
|
// make sure we get item out of index
|
||||||
//fs.deallocateItemStorage(folderPath, itemName);
|
//fs.deallocateItemStorage(folderPath, itemName);
|
||||||
invokeInstanceMethod("deallocateItemStorage", fs, new Class[] { String.class,
|
invokeInstanceMethod("deallocateItemStorage", fs,
|
||||||
String.class }, new Object[] { folderPath, itemName });
|
new Class[] { String.class, String.class },
|
||||||
|
new Object[] { folderPath, itemName });
|
||||||
}
|
}
|
||||||
++cnt;
|
++cnt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,16 @@ public class AskScript extends GhidraScript {
|
||||||
}
|
}
|
||||||
|
|
||||||
Program prog = askProgram("Please choose a program to open.");
|
Program prog = askProgram("Please choose a program to open.");
|
||||||
println("Program picked: " + prog.getName());
|
if (prog != null) {
|
||||||
|
// NOTE: if prog is not null script must release it when done using.
|
||||||
|
// This may also be accomplished via an overridden cleanup(boolean) method.
|
||||||
|
try {
|
||||||
|
println("Program picked: " + prog.getName());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
prog.release(this); // will remain open in tool if applicable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DomainFile domFile = askDomainFile("Which domain file would you like?");
|
DomainFile domFile = askDomainFile("Which domain file would you like?");
|
||||||
println("Domain file: " + domFile.getName());
|
println("Domain file: " + domFile.getName());
|
||||||
|
|
|
@ -38,19 +38,24 @@ public class CompareAnalysisScript extends GhidraScript {
|
||||||
if (otherProgram == null) {
|
if (otherProgram == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
println("\n\n****** COMPARING FUNCTIONS:\n");
|
try {
|
||||||
compareFunctions(otherProgram);
|
println("\n\n****** COMPARING FUNCTIONS:\n");
|
||||||
println("\n\n****** COMPARING STRINGS:\n");
|
compareFunctions(otherProgram);
|
||||||
compareStrings(otherProgram);
|
println("\n\n****** COMPARING STRINGS:\n");
|
||||||
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
|
compareStrings(otherProgram);
|
||||||
reportPercentDisassembled(currentProgram);
|
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
|
||||||
reportPercentDisassembled(otherProgram);
|
reportPercentDisassembled(currentProgram);
|
||||||
println("\n\n****** COMPARING SWITCH TABLES:\n");
|
reportPercentDisassembled(otherProgram);
|
||||||
compareSwitchTables(otherProgram);
|
println("\n\n****** COMPARING SWITCH TABLES:\n");
|
||||||
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
|
compareSwitchTables(otherProgram);
|
||||||
compareNoReturns(otherProgram);
|
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
|
||||||
println("\n\n****** COMPARING ERRORS:\n");
|
compareNoReturns(otherProgram);
|
||||||
compareErrors(otherProgram);
|
println("\n\n****** COMPARING ERRORS:\n");
|
||||||
|
compareErrors(otherProgram);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
otherProgram.release(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void compareFunctions(Program otherProgram) {
|
void compareFunctions(Program otherProgram) {
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class AnalysisStateInfo {
|
||||||
if (stateMap == null) {
|
if (stateMap == null) {
|
||||||
stateMap = new HashMap<>();
|
stateMap = new HashMap<>();
|
||||||
programStates.put(program, stateMap);
|
programStates.put(program, stateMap);
|
||||||
program.addCloseListener(() -> programStates.remove(program));
|
program.addCloseListener(doa -> programStates.remove(program));
|
||||||
}
|
}
|
||||||
stateMap.put(state.getClass(), state);
|
stateMap.put(state.getClass(), state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ import ghidra.util.task.*;
|
||||||
* Provides support for auto analysis tasks.
|
* Provides support for auto analysis tasks.
|
||||||
* Manages a pipeline or priority of tasks to run given some event has occurred.
|
* Manages a pipeline or priority of tasks to run given some event has occurred.
|
||||||
*/
|
*/
|
||||||
public class AutoAnalysisManager implements DomainObjectListener, DomainObjectClosedListener {
|
public class AutoAnalysisManager implements DomainObjectListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the shared thread pool that analyzers can uses to do parallel processing.
|
* The name of the shared thread pool that analyzers can uses to do parallel processing.
|
||||||
|
@ -145,7 +145,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||||
private AutoAnalysisManager(Program program) {
|
private AutoAnalysisManager(Program program) {
|
||||||
this.program = program;
|
this.program = program;
|
||||||
eventQueueID = program.createPrivateEventQueue(this, 500);
|
eventQueueID = program.createPrivateEventQueue(this, 500);
|
||||||
program.addCloseListener(this);
|
program.addCloseListener(dobj -> dispose());
|
||||||
initializeAnalyzers();
|
initializeAnalyzers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,11 +361,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||||
subType == ChangeManager.FUNCTION_CHANGED_RETURN;
|
subType == ChangeManager.FUNCTION_CHANGED_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void domainObjectClosed() {
|
|
||||||
dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||||
if (program == null) {
|
if (program == null) {
|
||||||
|
@ -961,10 +956,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginTool anyTool = null;
|
PluginTool anyTool = null;
|
||||||
Iterator<PluginTool> iterator = toolSet.iterator();
|
for (PluginTool tool : toolSet) {
|
||||||
while (iterator.hasNext()) {
|
|
||||||
PluginTool tool = iterator.next();
|
|
||||||
|
|
||||||
anyTool = tool;
|
anyTool = tool;
|
||||||
JFrame toolFrame = tool.getToolFrame();
|
JFrame toolFrame = tool.getToolFrame();
|
||||||
if (toolFrame != null && toolFrame.isActive()) {
|
if (toolFrame != null && toolFrame.isActive()) {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import ghidra.app.nav.Navigatable;
|
||||||
import ghidra.app.services.*;
|
import ghidra.app.services.*;
|
||||||
import ghidra.app.util.viewer.listingpanel.*;
|
import ghidra.app.util.viewer.listingpanel.*;
|
||||||
import ghidra.app.util.viewer.util.AddressIndexMap;
|
import ghidra.app.util.viewer.util.AddressIndexMap;
|
||||||
import ghidra.framework.model.DomainObjectClosedListener;
|
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
@ -510,30 +509,19 @@ public class MarkerManager implements MarkerService {
|
||||||
private final AddressColorCache colorCache = new AddressColorCache();
|
private final AddressColorCache colorCache = new AddressColorCache();
|
||||||
private final ColorBlender blender = new ColorBlender();
|
private final ColorBlender blender = new ColorBlender();
|
||||||
|
|
||||||
private final MarkerSetCache cache;
|
|
||||||
private final Program program;
|
|
||||||
private final DomainObjectClosedListener closeListener = this::programClosed;
|
|
||||||
|
|
||||||
public MarkerSetCacheEntry(MarkerSetCache cache, Program program) {
|
public MarkerSetCacheEntry(MarkerSetCache cache, Program program) {
|
||||||
this.cache = cache;
|
|
||||||
this.program = program;
|
|
||||||
/**
|
/**
|
||||||
* Use this close listener approach instead of plugin events, since we don't get a
|
* Use this close listener approach instead of plugin events, since we don't get a
|
||||||
* ProgramClosedPluginEvent when a trace view is closed, but we can listen for its
|
* ProgramClosedPluginEvent when a trace view is closed, but we can listen for its
|
||||||
* domain object closing, which works for plain programs, too.
|
* domain object closing, which works for plain programs, too.
|
||||||
*/
|
*/
|
||||||
program.addCloseListener(closeListener);
|
program.addCloseListener(dobj -> cache.programClosed(program));
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearColors() {
|
void clearColors() {
|
||||||
colorCache.clear();
|
colorCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void programClosed() {
|
|
||||||
program.removeCloseListener(closeListener);
|
|
||||||
cache.programClosed(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkerSetImpl getByName(String name) {
|
MarkerSetImpl getByName(String name) {
|
||||||
for (MarkerSetImpl set : markerSets) {
|
for (MarkerSetImpl set : markerSets) {
|
||||||
if (name.equals(set.getName())) {
|
if (name.equals(set.getName())) {
|
||||||
|
|
|
@ -1883,6 +1883,9 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* @param transformer the function to turn a String into a T
|
* @param transformer the function to turn a String into a T
|
||||||
* @param key the values used to create a key for lookup in the script properties file
|
* @param key the values used to create a key for lookup in the script properties file
|
||||||
* @return null if no value was found in the aforementioned sources
|
* @return null if no value was found in the aforementioned sources
|
||||||
|
* @throws IllegalArgumentException if the loaded String value cannot be parsed into a
|
||||||
|
* <code>T</code> or property not defined when in headless
|
||||||
|
* mode.
|
||||||
*/
|
*/
|
||||||
private <T> T loadAskValue(StringTransformer<T> transformer, String key) {
|
private <T> T loadAskValue(StringTransformer<T> transformer, String key) {
|
||||||
T value = loadAskValue(null, transformer, key);
|
T value = loadAskValue(null, transformer, key);
|
||||||
|
@ -1897,11 +1900,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* @param defaultValue an optional default value that will be used if no suitable
|
* @param defaultValue an optional default value that will be used if no suitable
|
||||||
* value can be found in script args or a properties file
|
* value can be found in script args or a properties file
|
||||||
* @param transformer the function to turn a String into a T
|
* @param transformer the function to turn a String into a T
|
||||||
* @param key the values used to create a key for lookup in the script properties file
|
* @param key the value property key used for lookup in the script properties file
|
||||||
* @return null if no value was found in the aforementioned sources
|
* @return null if no value was found in the aforementioned sources
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException if the loaded String value cannot be parsed into a
|
* @throws IllegalArgumentException if the loaded String value cannot be parsed into a
|
||||||
* <code>T</code>.
|
* <code>T</code> or property not defined when in headless
|
||||||
|
* mode and no defaultValue has been specified.
|
||||||
*/
|
*/
|
||||||
private <T> T loadAskValue(T defaultValue, StringTransformer<T> transformer, String key) {
|
private <T> T loadAskValue(T defaultValue, StringTransformer<T> transformer, String key) {
|
||||||
|
|
||||||
|
@ -2550,7 +2554,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address
|
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address
|
||||||
* specified in the .properties file
|
* specified in the .properties file
|
||||||
*/
|
*/
|
||||||
public Address askAddress(String title, String message, String defaultValue) throws CancelledException {
|
public Address askAddress(String title, String message, String defaultValue)
|
||||||
|
throws CancelledException {
|
||||||
|
|
||||||
String key = join(title, message);
|
String key = join(title, message);
|
||||||
|
|
||||||
|
@ -2683,7 +2688,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
*
|
*
|
||||||
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
|
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
|
||||||
* headless mode)
|
* headless mode)
|
||||||
* @return the user-specified Program
|
* @return the user-selected Program with this script as the consumer or null if a program was
|
||||||
|
* not selected. NOTE: It is very important that the program instance returned by this method
|
||||||
|
* ALWAYS be properly released when no longer needed. The script which invoked this method must be
|
||||||
|
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
|
||||||
|
* properly release the program may result in improper project disposal. If the program was
|
||||||
|
* opened by the tool, the tool will be a second consumer responsible for its own release.
|
||||||
* @throws VersionException if the Program is out-of-date from the version of GHIDRA
|
* @throws VersionException if the Program is out-of-date from the version of GHIDRA
|
||||||
* @throws IOException if there is an error accessing the Program's DomainObject
|
* @throws IOException if there is an error accessing the Program's DomainObject
|
||||||
* @throws CancelledException if the operation is cancelled
|
* @throws CancelledException if the operation is cancelled
|
||||||
|
@ -2693,33 +2703,34 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
public Program askProgram(String title)
|
public Program askProgram(String title)
|
||||||
throws VersionException, IOException, CancelledException {
|
throws VersionException, IOException, CancelledException {
|
||||||
|
|
||||||
DomainFile existingValue = loadAskValue(this::parseDomainFile, title);
|
DomainFile choice = loadAskValue(this::parseDomainFile, title);
|
||||||
if (isRunningHeadless()) {
|
if (!isRunningHeadless()) {
|
||||||
return (Program) existingValue.getDomainObject(this, false, false, monitor);
|
choice = doAsk(Program.class, title, "", choice, lastValue -> {
|
||||||
|
|
||||||
|
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
|
||||||
|
dtd.show();
|
||||||
|
if (dtd.wasCancelled()) {
|
||||||
|
throw new CancelledException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtd.getDomainFile();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainFile choice = doAsk(Program.class, title, "", existingValue, lastValue -> {
|
|
||||||
|
|
||||||
DataTreeDialog dtd = new DataTreeDialog(null, title, DataTreeDialog.OPEN);
|
|
||||||
dtd.show();
|
|
||||||
if (dtd.wasCancelled()) {
|
|
||||||
throw new CancelledException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return dtd.getDomainFile();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (choice == null) {
|
if (choice == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Program p = (Program) choice.getDomainObject(this, false, false, monitor);
|
||||||
|
|
||||||
PluginTool tool = state.getTool();
|
PluginTool tool = state.getTool();
|
||||||
if (tool == null) {
|
if (tool == null) {
|
||||||
return (Program) choice.getDomainObject(this, false, false, monitor);
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||||
return pm.openProgram(choice);
|
pm.openProgram(p);
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2768,10 +2779,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
*
|
*
|
||||||
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in headless
|
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in headless
|
||||||
* mode or when using .properties file)
|
* mode or when using .properties file)
|
||||||
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain
|
|
||||||
* file specified in the .properties file
|
|
||||||
* @return the user-selected domain file
|
* @return the user-selected domain file
|
||||||
* @throws CancelledException if the operation is cancelled
|
* @throws CancelledException if the operation is cancelled
|
||||||
|
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid domain
|
||||||
|
* file specified in the .properties file
|
||||||
*/
|
*/
|
||||||
public DomainFile askDomainFile(String title) throws CancelledException {
|
public DomainFile askDomainFile(String title) throws CancelledException {
|
||||||
|
|
||||||
|
@ -3015,8 +3026,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
throw new ImproperUseException(
|
throw new ImproperUseException(
|
||||||
"The askPassword() method can only be used when running headed Ghidra.");
|
"The askPassword() method can only be used when running headed Ghidra.");
|
||||||
}
|
}
|
||||||
PasswordDialog dialog =
|
PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null);
|
||||||
new PasswordDialog(title, null, null, prompt, null, null);
|
|
||||||
try {
|
try {
|
||||||
state.getTool().showDialog(dialog);
|
state.getTool().showDialog(dialog);
|
||||||
if (!dialog.okWasPressed()) {
|
if (!dialog.okWasPressed()) {
|
||||||
|
|
|
@ -878,7 +878,7 @@ public class HeadlessAnalyzer {
|
||||||
|
|
||||||
// Get parent folder to pass to GhidraScript
|
// Get parent folder to pass to GhidraScript
|
||||||
File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI())
|
File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI())
|
||||||
.getParentFile();
|
.getParentFile();
|
||||||
|
|
||||||
currScript = (GhidraScript) c.getConstructor().newInstance();
|
currScript = (GhidraScript) c.getConstructor().newInstance();
|
||||||
currScript.setScriptArgs(scriptArgs);
|
currScript.setScriptArgs(scriptArgs);
|
||||||
|
@ -1575,13 +1575,12 @@ public class HeadlessAnalyzer {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (options.readOnly) {
|
if (options.readOnly) {
|
||||||
Msg.info(this, "REPORT: Discarded file import due to readOnly option: " +
|
Msg.info(this,
|
||||||
loaded);
|
"REPORT: Discarded file import due to readOnly option: " + loaded);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Msg.info(this,
|
Msg.info(this, "REPORT: Discarded file import as a result of script " +
|
||||||
"REPORT: Discarded file import as a result of script " +
|
"activity or analysis timeout: " + loaded);
|
||||||
"activity or analysis timeout: " + loaded);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1627,9 +1626,9 @@ public class HeadlessAnalyzer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadResults<Program> loadPrograms(File file, String folderPath) throws VersionException,
|
private LoadResults<Program> loadPrograms(File file, String folderPath)
|
||||||
InvalidNameException, DuplicateNameException, CancelledException, IOException,
|
throws VersionException, InvalidNameException, DuplicateNameException,
|
||||||
LoadException {
|
CancelledException, IOException, LoadException {
|
||||||
MessageLog messageLog = new MessageLog();
|
MessageLog messageLog = new MessageLog();
|
||||||
|
|
||||||
if (options.loaderClass == null) {
|
if (options.loaderClass == null) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.*;
|
||||||
|
|
||||||
import generic.test.TestUtils;
|
import generic.test.TestUtils;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.store.FileSystem;
|
import ghidra.framework.store.FileSystem;
|
||||||
import ghidra.framework.store.FileSystemEventManager;
|
import ghidra.framework.store.FileSystemEventManager;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
@ -37,13 +38,13 @@ import ghidra.program.model.listing.Program;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private File privateProjectDir;
|
private File privateProjectDir;
|
||||||
private File sharedProjectDir;
|
private File sharedProjectDir;
|
||||||
private FileSystem sharedFS;
|
private FileSystem sharedFS;
|
||||||
private LocalFileSystem privateFS;
|
private LocalFileSystem privateFS;
|
||||||
private ProjectFileManager fileMgr;
|
private DefaultProjectData projectData;
|
||||||
private DomainFolder root;
|
private DomainFolder root;
|
||||||
private List<MyEvent> events = new ArrayList<>();
|
private List<MyEvent> events = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -83,9 +84,9 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||||
true, false, true);
|
true, false, true);
|
||||||
|
|
||||||
fileMgr = new ProjectFileManager(privateFS, sharedFS);
|
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||||
fileMgr.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
|
projectData.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
|
||||||
root = fileMgr.getRootFolder();
|
root = projectData.getRootFolder();
|
||||||
flushFileSystemEventsAndClearTestQueue();
|
flushFileSystemEventsAndClearTestQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
fileMgr.dispose();
|
projectData.dispose();
|
||||||
deleteAll(privateProjectDir);
|
deleteAll(privateProjectDir);
|
||||||
deleteAll(sharedProjectDir);
|
deleteAll(sharedProjectDir);
|
||||||
}
|
}
|
||||||
|
@ -136,9 +137,15 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetLocalProjectURL() {
|
||||||
|
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator()),
|
||||||
|
projectData.getLocalProjectURL());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRootFolder() throws Exception {
|
public void testGetRootFolder() throws Exception {
|
||||||
DomainFolder rootFolder = fileMgr.getRootFolder();
|
DomainFolder rootFolder = projectData.getRootFolder();
|
||||||
assertEquals("/", rootFolder.getPathname());
|
assertEquals("/", rootFolder.getPathname());
|
||||||
assertEquals(3, rootFolder.getFolders().length);
|
assertEquals(3, rootFolder.getFolders().length);
|
||||||
}
|
}
|
||||||
|
@ -146,11 +153,11 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testGetFolder() throws Exception {
|
public void testGetFolder() throws Exception {
|
||||||
|
|
||||||
DomainFolder rootFolder = fileMgr.getRootFolder();
|
DomainFolder rootFolder = projectData.getRootFolder();
|
||||||
DomainFolder df1 = fileMgr.getFolder("/");
|
DomainFolder df1 = projectData.getFolder("/");
|
||||||
DomainFolder df2 = fileMgr.getFolder("/a");
|
DomainFolder df2 = projectData.getFolder("/a");
|
||||||
DomainFolder df3 = fileMgr.getFolder("/a/y");
|
DomainFolder df3 = projectData.getFolder("/a/y");
|
||||||
DomainFolder df4 = fileMgr.getFolder("/a/x");
|
DomainFolder df4 = projectData.getFolder("/a/x");
|
||||||
|
|
||||||
assertNotNull(rootFolder);
|
assertNotNull(rootFolder);
|
||||||
assertEquals(rootFolder, df1);
|
assertEquals(rootFolder, df1);
|
||||||
|
@ -178,7 +185,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateFile() throws Exception {
|
public void testCreateFile() throws Exception {
|
||||||
DomainFolder folder = fileMgr.getFolder("/a");
|
DomainFolder folder = projectData.getFolder("/a");
|
||||||
folder.getFiles(); // visit folder to receive change events from this folder
|
folder.getFiles(); // visit folder to receive change events from this folder
|
||||||
flushFileSystemEventsAndClearTestQueue();
|
flushFileSystemEventsAndClearTestQueue();
|
||||||
|
|
||||||
|
@ -195,18 +202,18 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
assertEventsSize(2);
|
assertEventsSize(2);
|
||||||
checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null);
|
checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null);
|
||||||
|
|
||||||
DomainFile df = fileMgr.getFileByID(fileID1);
|
DomainFile df = projectData.getFileByID(fileID1);
|
||||||
assertNotNull(df);
|
assertNotNull(df);
|
||||||
assertEquals("file1", df.getName());
|
assertEquals("file1", df.getName());
|
||||||
assertTrue(!df.isVersioned());
|
assertTrue(!df.isVersioned());
|
||||||
|
|
||||||
df = fileMgr.getFileByID(fileID2);
|
df = projectData.getFileByID(fileID2);
|
||||||
assertNotNull(df2);
|
assertNotNull(df2);
|
||||||
assertEquals("file2", df.getName());
|
assertEquals("file2", df.getName());
|
||||||
|
|
||||||
df1.addToVersionControl("", false, TaskMonitor.DUMMY);
|
df1.addToVersionControl("", false, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
df = fileMgr.getFileByID(fileID1);
|
df = projectData.getFileByID(fileID1);
|
||||||
assertNotNull(df1);
|
assertNotNull(df1);
|
||||||
assertEquals("file1", df.getName());
|
assertEquals("file1", df.getName());
|
||||||
assertTrue(df.isVersioned());
|
assertTrue(df.isVersioned());
|
||||||
|
@ -216,7 +223,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testFileIndex() throws Exception {
|
public void testFileIndex() throws Exception {
|
||||||
|
|
||||||
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", fileMgr);
|
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", projectData);
|
||||||
assertNotNull(fileIndex);
|
assertNotNull(fileIndex);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -224,21 +231,21 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
(HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex);
|
(HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex);
|
||||||
assertNotNull(fileIdToPathIndex);
|
assertNotNull(fileIdToPathIndex);
|
||||||
|
|
||||||
DomainFolder folder = fileMgr.getFolder("/a");
|
DomainFolder folder = projectData.getFolder("/a");
|
||||||
|
|
||||||
DomainFile df1 = createFile(folder, "file1");
|
DomainFile df1 = createFile(folder, "file1");
|
||||||
String fileID = df1.getFileID();
|
String fileID = df1.getFileID();
|
||||||
|
|
||||||
assertEquals(df1, fileMgr.getFileByID(fileID));
|
assertEquals(df1, projectData.getFileByID(fileID));
|
||||||
|
|
||||||
// invalidate folder data to force search
|
// invalidate folder data to force search
|
||||||
|
|
||||||
GhidraFolderData rootFolderData = fileMgr.getRootFolderData();
|
GhidraFolderData rootFolderData = projectData.getRootFolderData();
|
||||||
rootFolderData.dispose();
|
rootFolderData.dispose();
|
||||||
|
|
||||||
assertTrue(fileIdToPathIndex.isEmpty()); // folder invalidation should cause map to clear
|
assertTrue(fileIdToPathIndex.isEmpty()); // folder invalidation should cause map to clear
|
||||||
|
|
||||||
assertEquals(df1, fileMgr.getFileByID(fileID));
|
assertEquals(df1, projectData.getFileByID(fileID));
|
||||||
|
|
||||||
assertFalse(fileIdToPathIndex.isEmpty()); // index should become populated
|
assertFalse(fileIdToPathIndex.isEmpty()); // index should become populated
|
||||||
}
|
}
|
||||||
|
@ -246,7 +253,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testFileIndexUndoCheckout() throws Exception {
|
public void testFileIndexUndoCheckout() throws Exception {
|
||||||
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
|
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
|
||||||
DomainFolder folder = fileMgr.getFolder("/a");
|
DomainFolder folder = projectData.getFolder("/a");
|
||||||
|
|
||||||
DomainFile df1 = createFile(folder, "file1");
|
DomainFile df1 = createFile(folder, "file1");
|
||||||
String fileID = df1.getFileID();
|
String fileID = df1.getFileID();
|
||||||
|
@ -265,7 +272,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
@Test
|
@Test
|
||||||
public void testFileIndexHijack() throws Exception {
|
public void testFileIndexHijack() throws Exception {
|
||||||
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
|
// TODO: This only tests the connected state - a remote file-system is required to test the disconnect/re-connected condition
|
||||||
DomainFolder folder = fileMgr.getFolder("/a");
|
DomainFolder folder = projectData.getFolder("/a");
|
||||||
folder.getFiles(); // visit folder to enable folder change listener
|
folder.getFiles(); // visit folder to enable folder change listener
|
||||||
|
|
||||||
// create shared file /a/file1 and keep checked-out
|
// create shared file /a/file1 and keep checked-out
|
||||||
|
@ -285,7 +292,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
df1.setName("file2");
|
df1.setName("file2");
|
||||||
|
|
||||||
DomainFile df2 = fileMgr.getFile("/a/file2");
|
DomainFile df2 = projectData.getFile("/a/file2");
|
||||||
|
|
||||||
assertTrue(!fileID.equals(df2.getFileID()));
|
assertTrue(!fileID.equals(df2.getFileID()));
|
||||||
|
|
||||||
|
@ -451,7 +458,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFolderRenamedEvent3() throws Exception {
|
public void testFolderRenamedEvent3() throws Exception {
|
||||||
fileMgr.getFolder("/a"); // force folder refresh to reduce event count
|
projectData.getFolder("/a"); // force folder refresh to reduce event count
|
||||||
flushFileSystemEventsAndClearTestQueue();
|
flushFileSystemEventsAndClearTestQueue();
|
||||||
|
|
||||||
// exists in localFS so "b" folder should not get created again
|
// exists in localFS so "b" folder should not get created again
|
||||||
|
@ -495,7 +502,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRenameFolder6() throws Exception {
|
public void testRenameFolder6() throws Exception {
|
||||||
DomainFolder aFolder = fileMgr.getFolder("/a");
|
DomainFolder aFolder = projectData.getFolder("/a");
|
||||||
assertNotNull(aFolder);
|
assertNotNull(aFolder);
|
||||||
aFolder.getFolders(); // visit folder to receive change events for it
|
aFolder.getFolders(); // visit folder to receive change events for it
|
||||||
|
|
||||||
|
@ -601,12 +608,12 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
|
|
||||||
// versioned folder was moved to /c/a, but private folder /a should still exist
|
// versioned folder was moved to /c/a, but private folder /a should still exist
|
||||||
|
|
||||||
GhidraFolder folder = (GhidraFolder) fileMgr.getFolder("/a");
|
GhidraFolder folder = (GhidraFolder) projectData.getFolder("/a");
|
||||||
assertNotNull(folder);
|
assertNotNull(folder);
|
||||||
assertTrue(folder.privateExists());
|
assertTrue(folder.privateExists());
|
||||||
assertFalse(folder.sharedExists());
|
assertFalse(folder.sharedExists());
|
||||||
|
|
||||||
folder = (GhidraFolder) fileMgr.getFolder("/c/a");
|
folder = (GhidraFolder) projectData.getFolder("/c/a");
|
||||||
assertNotNull(folder);
|
assertNotNull(folder);
|
||||||
assertFalse(folder.privateExists());
|
assertFalse(folder.privateExists());
|
||||||
assertTrue(folder.sharedExists());
|
assertTrue(folder.sharedExists());
|
|
@ -41,7 +41,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
private FileSystem sharedFS;
|
private FileSystem sharedFS;
|
||||||
private LocalFileSystem privateFS;
|
private LocalFileSystem privateFS;
|
||||||
|
|
||||||
private ProjectFileManager pfm;
|
private DefaultProjectData projectData;
|
||||||
private GhidraFolder root;
|
private GhidraFolder root;
|
||||||
|
|
||||||
public GhidraFileTest() {
|
public GhidraFileTest() {
|
||||||
|
@ -76,8 +76,8 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
false, false, true);
|
false, false, true);
|
||||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||||
true, false, true);
|
true, false, true);
|
||||||
pfm = new ProjectFileManager(privateFS, sharedFS);
|
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||||
root = pfm.getRootFolder();
|
root = projectData.getRootFolder();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,12 +88,12 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLocalURL() throws IOException {
|
public void testGetLocalProjectURL() throws IOException {
|
||||||
createDB(privateFS, "/a", "file1");
|
createDB(privateFS, "/a", "file1");
|
||||||
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"),
|
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", "xyz"),
|
||||||
pfm.getFile("/a/file1").getLocalProjectURL("xyz"));
|
projectData.getFile("/a/file1").getLocalProjectURL("xyz"));
|
||||||
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null),
|
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", null),
|
||||||
pfm.getFile("/a/file1").getLocalProjectURL(null));
|
projectData.getFile("/a/file1").getLocalProjectURL(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -345,7 +345,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private void refresh() throws IOException {
|
private void refresh() throws IOException {
|
||||||
// refresh everything regardless of visited state
|
// refresh everything regardless of visited state
|
||||||
pfm.refresh(true);
|
projectData.refresh(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteAll(File file) {
|
private void deleteAll(File file) {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import ghidra.framework.model.ProjectLocator;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
|
||||||
|
@ -31,6 +33,7 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
private LocalFileSystem sharedFS;
|
private LocalFileSystem sharedFS;
|
||||||
private LocalFileSystem privateFS;
|
private LocalFileSystem privateFS;
|
||||||
|
|
||||||
|
private DefaultProjectData projectData;
|
||||||
private GhidraFolder root;
|
private GhidraFolder root;
|
||||||
|
|
||||||
public GhidraFolderTest() {
|
public GhidraFolderTest() {
|
||||||
|
@ -68,8 +71,8 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
false, false, true);
|
false, false, true);
|
||||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||||
true, false, true);
|
true, false, true);
|
||||||
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
|
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||||
root = projectFileManager.getRootFolder();
|
root = projectData.getRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteTestFiles() {
|
private void deleteTestFiles() {
|
||||||
|
@ -82,6 +85,15 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
deleteTestFiles();
|
deleteTestFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetLocalProjectURL() {
|
||||||
|
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||||
|
assertEquals(GhidraURL.makeURL(projectLocator, "/a/y", null),
|
||||||
|
projectData.getFolder("/a/y").getLocalProjectURL());
|
||||||
|
assertEquals(GhidraURL.makeURL(projectLocator, "/a/x", null),
|
||||||
|
projectData.getFolder("/a/x").getLocalProjectURL());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetFolderNames() throws Exception {
|
public void testGetFolderNames() throws Exception {
|
||||||
GhidraFolder[] folders = root.getFolders();
|
GhidraFolder[] folders = root.getFolders();
|
||||||
|
|
|
@ -15,24 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.data;
|
package ghidra.framework.data;
|
||||||
|
|
||||||
import ghidra.framework.model.DomainFolder;
|
|
||||||
import ghidra.framework.model.Project;
|
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
|
||||||
import ghidra.util.InvalidNameException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
|
import ghidra.framework.model.DomainFolder;
|
||||||
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
import ghidra.util.InvalidNameException;
|
||||||
|
|
||||||
public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private File testRootDir;
|
private File testRootDir;
|
||||||
private File privateProjectDir;
|
private File privateProjectDir;
|
||||||
private File sharedProjectDir;
|
private File sharedProjectDir;
|
||||||
private DomainFolder root;
|
private DomainFolder root;
|
||||||
private Project project;
|
|
||||||
private LocalFileSystem sharedFS;
|
private LocalFileSystem sharedFS;
|
||||||
private LocalFileSystem privateFS;
|
private LocalFileSystem privateFS;
|
||||||
|
|
||||||
|
@ -52,8 +51,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
|
||||||
true, false, false);
|
true, false, false);
|
||||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true,
|
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true,
|
||||||
true, false, false);
|
true, false, false);
|
||||||
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
|
DefaultProjectData projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||||
root = projectFileManager.getRootFolder();
|
root = projectData.getRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -68,8 +67,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
|
||||||
private void deleteAll(File file) {
|
private void deleteAll(File file) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
File[] files = file.listFiles();
|
File[] files = file.listFiles();
|
||||||
for (int i = 0; i < files.length; i++) {
|
for (File file2 : files) {
|
||||||
deleteAll(files[i]);
|
deleteAll(file2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.delete();
|
file.delete();
|
||||||
|
|
|
@ -903,8 +903,8 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
//
|
//
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
Object projectFileManager = getInstanceField("fileManager", df);
|
Object projectData = getInstanceField("projectData", df);
|
||||||
invokeInstanceMethod("setDomainObject", projectFileManager,
|
invokeInstanceMethod("setDomainObject", projectData,
|
||||||
new Class[] { String.class, DomainObjectAdapter.class },
|
new Class[] { String.class, DomainObjectAdapter.class },
|
||||||
new Object[] { path, program }
|
new Object[] { path, program }
|
||||||
);
|
);
|
||||||
|
@ -962,8 +962,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree,
|
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, true);
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,18 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.project;
|
package ghidra.framework.project;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import generic.test.AbstractGenericTest;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.Project;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.model.ProjectLocator;
|
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
import ghidra.test.*;
|
||||||
import ghidra.test.ProjectTestUtils;
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for adding and a view to a project, and removing
|
* Test class for adding and a view to a project, and removing
|
||||||
|
@ -32,7 +34,7 @@ import ghidra.test.ProjectTestUtils;
|
||||||
*/
|
*/
|
||||||
public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest {
|
public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
private final static String DIRECTORY_NAME = AbstractGenericTest.getTestDirectoryPath();
|
private final static String DIRECTORY_NAME = getTestDirectoryPath();
|
||||||
private final static String PROJECT_NAME1 = "TestAddViewToProject";
|
private final static String PROJECT_NAME1 = "TestAddViewToProject";
|
||||||
private final static String PROJECT_VIEW1 = "TestView1";
|
private final static String PROJECT_VIEW1 = "TestView1";
|
||||||
private final static String PROJECT_VIEW2 = "TestView2";
|
private final static String PROJECT_VIEW2 = "TestView2";
|
||||||
|
@ -52,24 +54,9 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
|
||||||
ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2);
|
ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Do the test.
|
|
||||||
* @param args same args that are passed to RegressionTester.main()
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddToView() throws Exception {
|
public void testAddToView() throws Exception {
|
||||||
|
|
||||||
// String filename = System.getProperty("user.dir") +
|
|
||||||
// File.separator + "testGhidraPreferences";
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// Preferences.load(filename);
|
|
||||||
//
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Preferences.setFilename(filename);
|
|
||||||
|
|
||||||
// make sure we have projects to use as the project view...
|
// make sure we have projects to use as the project view...
|
||||||
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close();
|
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close();
|
||||||
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close();
|
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close();
|
||||||
|
@ -87,12 +74,12 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
|
||||||
// validate the view was added to project
|
// validate the view was added to project
|
||||||
ProjectLocator[] projViews = project.getProjectViews();
|
ProjectLocator[] projViews = project.getProjectViews();
|
||||||
for (ProjectLocator projView : projViews) {
|
for (ProjectLocator projView : projViews) {
|
||||||
System.out.println("added view: " + projView);
|
Msg.debug(this, "** added view: " + projView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the view...
|
// remove the view...
|
||||||
project.removeProjectView(view);
|
project.removeProjectView(view);
|
||||||
System.out.println("removed view: " + view);
|
Msg.debug(this, "** removed view: " + view);
|
||||||
|
|
||||||
projViews = project.getProjectViews();
|
projViews = project.getProjectViews();
|
||||||
for (ProjectLocator projView : projViews) {
|
for (ProjectLocator projView : projViews) {
|
||||||
|
@ -106,4 +93,59 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCloseViewWithOpenProgram() throws Exception {
|
||||||
|
|
||||||
|
DomainObject dobj = null;
|
||||||
|
|
||||||
|
// make sure we have projects to use as the project view...
|
||||||
|
Project project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1);
|
||||||
|
try {
|
||||||
|
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
|
||||||
|
DomainFolder rootFolder = project.getProjectData().getRootFolder();
|
||||||
|
rootFolder.createFile("Test", builder.getProgram(), TaskMonitor.DUMMY);
|
||||||
|
builder.dispose();
|
||||||
|
project.close();
|
||||||
|
|
||||||
|
// get project (create it if it doesn't exist...)
|
||||||
|
project = ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_NAME1);
|
||||||
|
|
||||||
|
URL view = GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW1);
|
||||||
|
DefaultProjectData projectData =
|
||||||
|
(DefaultProjectData) project.addProjectView(view, true);
|
||||||
|
Msg.debug(this, "** added view: " + view);
|
||||||
|
assertNotNull(projectData);
|
||||||
|
|
||||||
|
DomainFile f = projectData.getFile("/Test");
|
||||||
|
assertNotNull(f);
|
||||||
|
|
||||||
|
// Open file and hold onto
|
||||||
|
dobj = f.getDomainObject(this, true, false, TaskMonitor.DUMMY);
|
||||||
|
Msg.debug(this, "** opened program: " + f);
|
||||||
|
|
||||||
|
assertFalse(projectData.isClosed());
|
||||||
|
assertFalse(projectData.isDisposed());
|
||||||
|
|
||||||
|
// remove the view while program open...
|
||||||
|
project.removeProjectView(view);
|
||||||
|
Msg.debug(this, "** removed view: " + view);
|
||||||
|
|
||||||
|
assertTrue(projectData.isClosed());
|
||||||
|
assertFalse(projectData.isDisposed());
|
||||||
|
|
||||||
|
Msg.debug(this, "** releasing program: " + f);
|
||||||
|
dobj.release(this);
|
||||||
|
dobj = null;
|
||||||
|
|
||||||
|
assertTrue(projectData.isClosed());
|
||||||
|
assertTrue(projectData.isDisposed());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (dobj != null) {
|
||||||
|
dobj.release(this);
|
||||||
|
}
|
||||||
|
project.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.Set;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import generic.test.AbstractGTest;
|
import generic.test.AbstractGTest;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSpace;
|
import ghidra.program.model.address.AddressSpace;
|
||||||
|
@ -57,8 +57,8 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
projectLocator = new ProjectLocator(TEMP, "Test");
|
projectLocator = new ProjectLocator(TEMP, "Test");
|
||||||
project = TestProjectManager.get().createProject(projectLocator, null, true);
|
project = TestProjectManager.get().createProject(projectLocator, null, true);
|
||||||
dataDir =
|
dataDir =
|
||||||
new File(projectLocator.getProjectDir(), ProjectFileManager.INDEXED_DATA_FOLDER_NAME);
|
new File(projectLocator.getProjectDir(), DefaultProjectData.INDEXED_DATA_FOLDER_NAME);
|
||||||
userDir = new File(projectLocator.getProjectDir(), ProjectFileManager.USER_FOLDER_NAME);
|
userDir = new File(projectLocator.getProjectDir(), DefaultProjectData.USER_FOLDER_NAME);
|
||||||
|
|
||||||
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
|
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
|
||||||
df = project.getProjectData()
|
df = project.getProjectData()
|
||||||
|
|
|
@ -81,8 +81,8 @@ public class FakeSharedProject {
|
||||||
// Note: this how we share multiple projects
|
// Note: this how we share multiple projects
|
||||||
void setVersionedFileSystem(LocalFileSystem fs) {
|
void setVersionedFileSystem(LocalFileSystem fs) {
|
||||||
|
|
||||||
ProjectFileManager fm = getProjectFileManager();
|
DefaultProjectData pd = getProjectData();
|
||||||
invokeInstanceMethod("setVersionedFileSystem", fm, argTypes(FileSystem.class), args(fs));
|
invokeInstanceMethod("setVersionedFileSystem", pd, argTypes(FileSystem.class), args(fs));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,12 +94,12 @@ public class FakeSharedProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the project file manager
|
* Gets the project data instance
|
||||||
*
|
*
|
||||||
* @return the project file manager
|
* @return the project data instance
|
||||||
*/
|
*/
|
||||||
public ProjectFileManager getProjectFileManager() {
|
public DefaultProjectData getProjectData() {
|
||||||
return (ProjectFileManager) gProject.getProjectData();
|
return (DefaultProjectData) gProject.getProjectData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,8 +108,8 @@ public class FakeSharedProject {
|
||||||
* @return the root folder of this project
|
* @return the root folder of this project
|
||||||
*/
|
*/
|
||||||
public RootGhidraFolder getRootFolder() {
|
public RootGhidraFolder getRootFolder() {
|
||||||
ProjectFileManager pfm = getProjectFileManager();
|
DefaultProjectData pd = getProjectData();
|
||||||
return (RootGhidraFolder) pfm.getRootFolder();
|
return (RootGhidraFolder) pd.getRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,6 +181,7 @@ public class FakeSharedProject {
|
||||||
* <li>calling {@link #addDomainFile(String)}</li>
|
* <li>calling {@link #addDomainFile(String)}</li>
|
||||||
* <li>Adding a versioned file to another project that shares the same repo with this project</li>
|
* <li>Adding a versioned file to another project that shares the same repo with this project</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* @param parentPath the parent folder path
|
||||||
* @param filename the filename
|
* @param filename the filename
|
||||||
* @return the file
|
* @return the file
|
||||||
*/
|
*/
|
||||||
|
@ -194,8 +195,7 @@ public class FakeSharedProject {
|
||||||
* Creates a folder by the given name in the given parent folder, creating the parent
|
* Creates a folder by the given name in the given parent folder, creating the parent
|
||||||
* folder if needed
|
* folder if needed
|
||||||
*
|
*
|
||||||
* @param parentPath the parent folder path
|
* @param path the full path of the folder to create
|
||||||
* @param name the name of the folder to create
|
|
||||||
* @return the created folder
|
* @return the created folder
|
||||||
* @throws Exception if there are any exceptions creating the folder
|
* @throws Exception if there are any exceptions creating the folder
|
||||||
*/
|
*/
|
||||||
|
@ -367,7 +367,7 @@ public class FakeSharedProject {
|
||||||
* @see FakeRepository#dispose()
|
* @see FakeRepository#dispose()
|
||||||
*/
|
*/
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
ProjectLocator projectLocator = getProjectFileManager().getProjectLocator();
|
ProjectLocator projectLocator = getProjectData().getProjectLocator();
|
||||||
programManager.disposeOpenPrograms();
|
programManager.disposeOpenPrograms();
|
||||||
gProject.close();
|
gProject.close();
|
||||||
FileUtilities.deleteDir(projectLocator.getProjectDir());
|
FileUtilities.deleteDir(projectLocator.getProjectDir());
|
||||||
|
@ -388,7 +388,7 @@ public class FakeSharedProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectLocator pl = df.getProjectLocator();
|
ProjectLocator pl = df.getProjectLocator();
|
||||||
ProjectLocator mypl = getProjectFileManager().getProjectLocator();
|
ProjectLocator mypl = getProjectData().getProjectLocator();
|
||||||
if (!pl.equals(mypl)) {
|
if (!pl.equals(mypl)) {
|
||||||
throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " +
|
throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " +
|
||||||
mypl.getName() + "\nYou must call addDomainFile(filename).");
|
mypl.getName() + "\nYou must call addDomainFile(filename).");
|
||||||
|
@ -397,9 +397,8 @@ public class FakeSharedProject {
|
||||||
|
|
||||||
private void waitForFileSystemEvents() {
|
private void waitForFileSystemEvents() {
|
||||||
LocalFileSystem versionedFileSystem = getVersionedFileSystem();
|
LocalFileSystem versionedFileSystem = getVersionedFileSystem();
|
||||||
FileSystemEventManager eventManager =
|
FileSystemEventManager eventManager = (FileSystemEventManager) TestUtils
|
||||||
(FileSystemEventManager) TestUtils.getInstanceField("eventManager",
|
.getInstanceField("eventManager", versionedFileSystem);
|
||||||
versionedFileSystem);
|
|
||||||
|
|
||||||
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
@ -444,16 +443,16 @@ public class FakeSharedProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem getVersionedFileSystem() {
|
LocalFileSystem getVersionedFileSystem() {
|
||||||
ProjectFileManager fileManager = getProjectFileManager();
|
DefaultProjectData projectData = getProjectData();
|
||||||
LocalFileSystem fs =
|
LocalFileSystem fs =
|
||||||
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", fileManager);
|
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", projectData);
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
void refresh() {
|
||||||
ProjectFileManager fileManager = getProjectFileManager();
|
DefaultProjectData projectData = getProjectData();
|
||||||
try {
|
try {
|
||||||
fileManager.refresh(true);
|
projectData.refresh(true);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
|
|
|
@ -33,215 +33,219 @@ import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.mem.*;
|
import ghidra.program.model.mem.*;
|
||||||
import ghidra.program.model.symbol.*;
|
import ghidra.program.model.symbol.*;
|
||||||
|
|
||||||
|
|
||||||
public class MergeTwoProgramsScript extends GhidraScript {
|
public class MergeTwoProgramsScript extends GhidraScript {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void run() throws Exception {
|
protected void run() throws Exception {
|
||||||
|
|
||||||
if ( currentProgram == null ) {
|
if (currentProgram == null) {
|
||||||
printerr( "Please open a program first!" );
|
printerr("Please open a program first!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Program otherProgram = askProgram( "Select program from which to merge: " );
|
Program otherProgram = askProgram("Select program from which to merge: ");
|
||||||
|
|
||||||
if ( otherProgram == null ) {
|
if (otherProgram == null) {
|
||||||
printerr( "Please select the other program first!" );
|
printerr("Please select the other program first!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !currentProgram.getLanguage().equals( otherProgram.getLanguage() ) ) {
|
try {
|
||||||
printerr( "Incompatible program languages!" );
|
|
||||||
return;
|
if (!currentProgram.getLanguage().equals(otherProgram.getLanguage())) {
|
||||||
|
printerr("Incompatible program languages!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentProgram.getMemory().intersects(otherProgram.getMemory())) {
|
||||||
|
printerr("Memory map of current program must be disjoint from other program!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openProgram(currentProgram);
|
||||||
|
|
||||||
|
mergeMemory(currentProgram, otherProgram);
|
||||||
|
mergeSymbols(currentProgram, otherProgram);
|
||||||
|
mergeBookmarks(currentProgram, otherProgram);
|
||||||
|
mergeComments(currentProgram, otherProgram);
|
||||||
|
mergeData(currentProgram, otherProgram);
|
||||||
|
mergeInstructions(currentProgram, otherProgram);
|
||||||
|
mergeEquates(currentProgram, otherProgram);
|
||||||
|
mergeReferences(currentProgram, otherProgram);
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
if ( currentProgram.getMemory().intersects( otherProgram.getMemory() ) ) {
|
otherProgram.release(this);
|
||||||
printerr( "Memory map of current program must be disjoint from other program!" );
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openProgram( currentProgram );
|
|
||||||
|
|
||||||
mergeMemory ( currentProgram, otherProgram );
|
|
||||||
mergeSymbols ( currentProgram, otherProgram );
|
|
||||||
mergeBookmarks ( currentProgram, otherProgram );
|
|
||||||
mergeComments ( currentProgram, otherProgram );
|
|
||||||
mergeData ( currentProgram, otherProgram );
|
|
||||||
mergeInstructions( currentProgram, otherProgram );
|
|
||||||
mergeEquates ( currentProgram, otherProgram );
|
|
||||||
mergeReferences ( currentProgram, otherProgram );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeReferences( Program currProgram, Program otherProgram ) {
|
private void mergeReferences(Program currProgram, Program otherProgram) {
|
||||||
monitor.setMessage( "Merging references..." );
|
monitor.setMessage("Merging references...");
|
||||||
ReferenceManager currentReferenceManager = currProgram.getReferenceManager();
|
ReferenceManager currentReferenceManager = currProgram.getReferenceManager();
|
||||||
ReferenceManager otherReferenceManager = otherProgram.getReferenceManager();
|
ReferenceManager otherReferenceManager = otherProgram.getReferenceManager();
|
||||||
ReferenceIterator otherReferenceIterator = otherReferenceManager.getReferenceIterator( otherProgram.getMinAddress() );
|
ReferenceIterator otherReferenceIterator =
|
||||||
while ( otherReferenceIterator.hasNext() ) {
|
otherReferenceManager.getReferenceIterator(otherProgram.getMinAddress());
|
||||||
if ( monitor.isCancelled() ) {
|
while (otherReferenceIterator.hasNext()) {
|
||||||
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Reference otherReference = otherReferenceIterator.next();
|
Reference otherReference = otherReferenceIterator.next();
|
||||||
if ( otherReference.isStackReference() ) {
|
if (otherReference.isStackReference()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
currentReferenceManager.addReference( otherReference );
|
currentReferenceManager.addReference(otherReference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeInstructions( Program currProgram, Program otherProgram ) {
|
private void mergeInstructions(Program currProgram, Program otherProgram) {
|
||||||
monitor.setMessage( "Merging instructions..." );
|
monitor.setMessage("Merging instructions...");
|
||||||
Listing currentListing = currProgram.getListing();
|
Listing currentListing = currProgram.getListing();
|
||||||
Listing otherListing = otherProgram.getListing();
|
Listing otherListing = otherProgram.getListing();
|
||||||
InstructionIterator otherInstructions = otherListing.getInstructions( true );
|
InstructionIterator otherInstructions = otherListing.getInstructions(true);
|
||||||
while ( otherInstructions.hasNext() ) {
|
while (otherInstructions.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Instruction otherInstruction = otherInstructions.next();
|
Instruction otherInstruction = otherInstructions.next();
|
||||||
if ( currentListing.isUndefined( otherInstruction.getMinAddress(), otherInstruction.getMaxAddress() ) ) {
|
if (currentListing.isUndefined(otherInstruction.getMinAddress(),
|
||||||
disassemble( otherInstruction.getMinAddress() );
|
otherInstruction.getMaxAddress())) {
|
||||||
|
disassemble(otherInstruction.getMinAddress());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeEquates( Program currProgram, Program otherProgram ) throws Exception {
|
private void mergeEquates(Program currProgram, Program otherProgram) throws Exception {
|
||||||
monitor.setMessage( "Merging equates..." );
|
monitor.setMessage("Merging equates...");
|
||||||
EquateTable currentEquateTable = currProgram.getEquateTable();
|
EquateTable currentEquateTable = currProgram.getEquateTable();
|
||||||
EquateTable otherEquateTable = otherProgram.getEquateTable();
|
EquateTable otherEquateTable = otherProgram.getEquateTable();
|
||||||
Iterator<Equate> otherEquates = otherEquateTable.getEquates();
|
Iterator<Equate> otherEquates = otherEquateTable.getEquates();
|
||||||
while ( otherEquates.hasNext() ) {
|
while (otherEquates.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Equate otherEquate = otherEquates.next();
|
Equate otherEquate = otherEquates.next();
|
||||||
Equate currentEquate = currentEquateTable.createEquate( otherEquate.getName(), otherEquate.getValue() );
|
Equate currentEquate =
|
||||||
EquateReference [] otherEquateReferences = otherEquate.getReferences();
|
currentEquateTable.createEquate(otherEquate.getName(), otherEquate.getValue());
|
||||||
for ( EquateReference otherEquateReference : otherEquateReferences ) {
|
EquateReference[] otherEquateReferences = otherEquate.getReferences();
|
||||||
if ( monitor.isCancelled() ) {
|
for (EquateReference otherEquateReference : otherEquateReferences) {
|
||||||
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
currentEquate.addReference( otherEquateReference.getAddress(), otherEquateReference.getOpIndex() );
|
currentEquate.addReference(otherEquateReference.getAddress(),
|
||||||
|
otherEquateReference.getOpIndex());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeData( Program currProgram, Program otherProgram ) throws Exception {
|
private void mergeData(Program currProgram, Program otherProgram) throws Exception {
|
||||||
monitor.setMessage( "Merging data..." );
|
monitor.setMessage("Merging data...");
|
||||||
Listing currentListing = currProgram.getListing();
|
Listing currentListing = currProgram.getListing();
|
||||||
Listing otherListing = otherProgram.getListing();
|
Listing otherListing = otherProgram.getListing();
|
||||||
DataIterator otherDataIterator = otherListing.getDefinedData( true );
|
DataIterator otherDataIterator = otherListing.getDefinedData(true);
|
||||||
while ( otherDataIterator.hasNext() ) {
|
while (otherDataIterator.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Data otherData = otherDataIterator.next();
|
Data otherData = otherDataIterator.next();
|
||||||
if ( currentListing.isUndefined( otherData.getMinAddress(), otherData.getMaxAddress() ) ) {
|
if (currentListing.isUndefined(otherData.getMinAddress(), otherData.getMaxAddress())) {
|
||||||
currentListing.createData( otherData.getMinAddress(), otherData.getDataType() );
|
currentListing.createData(otherData.getMinAddress(), otherData.getDataType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeComments( Program currProgram, Program otherProgram ) throws Exception {
|
private void mergeComments(Program currProgram, Program otherProgram) throws Exception {
|
||||||
monitor.setMessage( "Merging comments..." );
|
monitor.setMessage("Merging comments...");
|
||||||
int [] commentTypes = {
|
int[] commentTypes = { CodeUnit.EOL_COMMENT, CodeUnit.PRE_COMMENT, CodeUnit.POST_COMMENT,
|
||||||
CodeUnit.EOL_COMMENT,
|
CodeUnit.PLATE_COMMENT, CodeUnit.REPEATABLE_COMMENT, };
|
||||||
CodeUnit.PRE_COMMENT,
|
|
||||||
CodeUnit.POST_COMMENT,
|
|
||||||
CodeUnit.PLATE_COMMENT,
|
|
||||||
CodeUnit.REPEATABLE_COMMENT,
|
|
||||||
};
|
|
||||||
Listing currentListing = currProgram.getListing();
|
Listing currentListing = currProgram.getListing();
|
||||||
Listing otherListing = otherProgram.getListing();
|
Listing otherListing = otherProgram.getListing();
|
||||||
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits( true );
|
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits(true);
|
||||||
while ( otherCodeUnits.hasNext() ) {
|
while (otherCodeUnits.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
CodeUnit otherCodeUnit = otherCodeUnits.next();
|
CodeUnit otherCodeUnit = otherCodeUnits.next();
|
||||||
for ( int commentType : commentTypes ) {
|
for (int commentType : commentTypes) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
String otherComment = otherCodeUnit.getComment( commentType );
|
String otherComment = otherCodeUnit.getComment(commentType);
|
||||||
if ( otherComment != null ) {
|
if (otherComment != null) {
|
||||||
currentListing.setComment( otherCodeUnit.getAddress(), commentType, otherComment );
|
currentListing.setComment(otherCodeUnit.getAddress(), commentType,
|
||||||
|
otherComment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeBookmarks( Program currProgram, Program otherProgram ) {
|
private void mergeBookmarks(Program currProgram, Program otherProgram) {
|
||||||
monitor.setMessage( "Merging bookmarks..." );
|
monitor.setMessage("Merging bookmarks...");
|
||||||
BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager();
|
BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager();
|
||||||
BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager();
|
BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager();
|
||||||
Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator();
|
Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator();
|
||||||
while ( otherBookmarks.hasNext() ) {
|
while (otherBookmarks.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Bookmark otherBookmark = otherBookmarks.next();
|
Bookmark otherBookmark = otherBookmarks.next();
|
||||||
currentBookmarkManager.setBookmark( otherBookmark.getAddress(),
|
currentBookmarkManager.setBookmark(otherBookmark.getAddress(),
|
||||||
otherBookmark.getTypeString(),
|
otherBookmark.getTypeString(), otherBookmark.getCategory(),
|
||||||
otherBookmark.getCategory(),
|
otherBookmark.getComment());
|
||||||
otherBookmark.getComment() );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeSymbols( Program currProgram, Program otherProgram ) throws Exception {
|
private void mergeSymbols(Program currProgram, Program otherProgram) throws Exception {
|
||||||
monitor.setMessage( "Merging symbols..." );
|
monitor.setMessage("Merging symbols...");
|
||||||
SymbolTable currentSymbolTable = currProgram.getSymbolTable();
|
SymbolTable currentSymbolTable = currProgram.getSymbolTable();
|
||||||
SymbolTable otherSymbolTable = otherProgram.getSymbolTable();
|
SymbolTable otherSymbolTable = otherProgram.getSymbolTable();
|
||||||
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols( false );
|
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols(false);
|
||||||
while ( otherSymbols.hasNext() ) {
|
while (otherSymbols.hasNext()) {
|
||||||
if ( monitor.isCancelled() ) {
|
if (monitor.isCancelled()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Symbol otherSymbol = otherSymbols.next();
|
Symbol otherSymbol = otherSymbols.next();
|
||||||
if ( otherSymbol.isDynamic() ) {
|
if (otherSymbol.isDynamic()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Namespace otherNamespace = otherSymbol.getParentNamespace();
|
Namespace otherNamespace = otherSymbol.getParentNamespace();
|
||||||
Namespace currentNamespace = mirrorNamespace( currProgram, otherProgram, otherNamespace );
|
Namespace currentNamespace =
|
||||||
if ( otherSymbol.getSymbolType() == SymbolType.FUNCTION ) {
|
mirrorNamespace(currProgram, otherProgram, otherNamespace);
|
||||||
Function otherFunction = otherProgram.getListing().getFunctionAt( otherSymbol.getAddress() );
|
if (otherSymbol.getSymbolType() == SymbolType.FUNCTION) {
|
||||||
currProgram.getListing().createFunction( otherSymbol.getName(),
|
Function otherFunction =
|
||||||
currentNamespace,
|
otherProgram.getListing().getFunctionAt(otherSymbol.getAddress());
|
||||||
otherFunction.getEntryPoint(),
|
currProgram.getListing()
|
||||||
otherFunction.getBody(),
|
.createFunction(otherSymbol.getName(), currentNamespace,
|
||||||
SourceType.USER_DEFINED );
|
otherFunction.getEntryPoint(), otherFunction.getBody(),
|
||||||
|
SourceType.USER_DEFINED);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
currentSymbolTable.createLabel( otherSymbol.getAddress(),
|
currentSymbolTable.createLabel(otherSymbol.getAddress(), otherSymbol.getName(),
|
||||||
otherSymbol.getName(),
|
currentNamespace, SourceType.USER_DEFINED);
|
||||||
currentNamespace,
|
|
||||||
SourceType.USER_DEFINED );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception e ) {
|
catch (Exception e) {
|
||||||
printerr( "Unable to create symbol: " + otherSymbol.getName() );
|
printerr("Unable to create symbol: " + otherSymbol.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Namespace mirrorNamespace( Program currProgram, Program otherProgram, Namespace otherNamespace ) throws Exception {
|
private Namespace mirrorNamespace(Program currProgram, Program otherProgram,
|
||||||
if ( otherNamespace == null ) {
|
Namespace otherNamespace) throws Exception {
|
||||||
|
if (otherNamespace == null) {
|
||||||
return currProgram.getGlobalNamespace();
|
return currProgram.getGlobalNamespace();
|
||||||
}
|
}
|
||||||
SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script!
|
SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script!
|
||||||
try {
|
try {
|
||||||
source = otherNamespace.getSymbol().getSource();
|
source = otherNamespace.getSymbol().getSource();
|
||||||
}
|
}
|
||||||
catch ( Exception e ) {
|
catch (Exception e) {
|
||||||
}
|
}
|
||||||
return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null,
|
return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null,
|
||||||
currProgram, source);
|
currProgram, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mergeMemory( Program currProgram, Program otherProgram ) throws Exception {
|
private void mergeMemory(Program currProgram, Program otherProgram) throws Exception {
|
||||||
monitor.setMessage( "Merging memory..." );
|
monitor.setMessage("Merging memory...");
|
||||||
Memory otherMemory = otherProgram.getMemory();
|
Memory otherMemory = otherProgram.getMemory();
|
||||||
MemoryBlock[] otherBlocks = otherMemory.getBlocks();
|
MemoryBlock[] otherBlocks = otherMemory.getBlocks();
|
||||||
MessageLog log = new MessageLog();
|
MessageLog log = new MessageLog();
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
// data and and then save the session.
|
// data and and then save the session.
|
||||||
//@category Examples.Version Tracking
|
//@category Examples.Version Tracking
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
|
@ -32,6 +31,21 @@ import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.task.TaskLauncher;
|
import ghidra.util.task.TaskLauncher;
|
||||||
|
|
||||||
public class AutoVersionTrackingScript extends GhidraScript {
|
public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
|
|
||||||
|
private Program sourceProgram;
|
||||||
|
private Program destinationProgram;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup(boolean success) {
|
||||||
|
if (sourceProgram != null && sourceProgram.isUsedBy(this)) {
|
||||||
|
sourceProgram.release(this);
|
||||||
|
}
|
||||||
|
if (destinationProgram != null && destinationProgram.isUsedBy(this)) {
|
||||||
|
destinationProgram.release(this);
|
||||||
|
}
|
||||||
|
super.cleanup(success);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception {
|
public void run() throws Exception {
|
||||||
|
|
||||||
|
@ -39,9 +53,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
askProjectFolder("Please choose a folder for your Version Tracking session.");
|
askProjectFolder("Please choose a folder for your Version Tracking session.");
|
||||||
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
||||||
|
|
||||||
Program sourceProgram;
|
|
||||||
Program destinationProgram;
|
|
||||||
|
|
||||||
boolean isCurrentProgramSourceProg = askYesNo("Current Program Source Program?",
|
boolean isCurrentProgramSourceProg = askYesNo("Current Program Source Program?",
|
||||||
"Is the current program your source program?");
|
"Is the current program your source program?");
|
||||||
|
|
||||||
|
@ -54,6 +65,10 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
sourceProgram = askProgram("Please select the source (existing annotated) program");
|
sourceProgram = askProgram("Please select the source (existing annotated) program");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceProgram == null || destinationProgram == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Need to end the script transaction or it interferes with vt things that need locks
|
// Need to end the script transaction or it interferes with vt things that need locks
|
||||||
end(true);
|
end(true);
|
||||||
|
|
||||||
|
@ -81,9 +96,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||||
|
|
||||||
public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) {
|
public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) {
|
||||||
List<Plugin> list = tool.getManagedPlugins();
|
List<Plugin> list = tool.getManagedPlugins();
|
||||||
Iterator<Plugin> it = list.iterator();
|
for (Plugin p : list) {
|
||||||
while (it.hasNext()) {
|
|
||||||
Plugin p = it.next();
|
|
||||||
if (p.getClass() == c) {
|
if (p.getClass() == c) {
|
||||||
return c.cast(p);
|
return c.cast(p);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
// data and and then save the session.
|
// data and and then save the session.
|
||||||
//@category Examples.Version Tracking
|
//@category Examples.Version Tracking
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.feature.vt.api.correlator.program.*;
|
import ghidra.feature.vt.api.correlator.program.*;
|
||||||
import ghidra.feature.vt.api.db.VTSessionDB;
|
import ghidra.feature.vt.api.db.VTSessionDB;
|
||||||
|
@ -31,17 +34,35 @@ import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.Program;
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
|
public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
|
||||||
|
|
||||||
|
private Program sourceProgram;
|
||||||
|
private Program destinationProgram;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup(boolean success) {
|
||||||
|
if (sourceProgram != null) {
|
||||||
|
sourceProgram.release(this);
|
||||||
|
}
|
||||||
|
if (destinationProgram != null) {
|
||||||
|
destinationProgram.release(this);
|
||||||
|
}
|
||||||
|
super.cleanup(success);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Exception {
|
public void run() throws Exception {
|
||||||
DomainFolder folder =
|
DomainFolder folder =
|
||||||
askProjectFolder("Please choose a folder for the session domain object");
|
askProjectFolder("Please choose a folder for the session domain object");
|
||||||
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
||||||
Program sourceProgram = askProgram("Please select the source (existing annotated) program");
|
sourceProgram = askProgram("Please select the source (existing annotated) program");
|
||||||
Program destinationProgram = askProgram("Please select the destination (new) program");
|
if (sourceProgram == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
destinationProgram = askProgram("Please select the destination (new) program");
|
||||||
|
if (destinationProgram == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
VTSession session =
|
VTSession session =
|
||||||
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
|
VTSessionDB.createVTSession(name, sourceProgram, destinationProgram, this);
|
||||||
|
@ -58,7 +79,8 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
|
||||||
|
|
||||||
// should we have convenience methods in VTCorrelator that don't
|
// should we have convenience methods in VTCorrelator that don't
|
||||||
// take address sets, thus implying the entire address space should be used?
|
// take address sets, thus implying the entire address space should be used?
|
||||||
AddressSetView sourceAddressSet = sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
|
AddressSetView sourceAddressSet =
|
||||||
|
sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||||
AddressSetView destinationAddressSet =
|
AddressSetView destinationAddressSet =
|
||||||
destinationProgram.getMemory().getLoadedAndInitializedAddressSet();
|
destinationProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||||
|
|
||||||
|
@ -91,17 +113,16 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
|
||||||
private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram,
|
private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram,
|
||||||
VTSession session, PluginTool serviceProvider, VTAssociationManager manager,
|
VTSession session, PluginTool serviceProvider, VTAssociationManager manager,
|
||||||
AddressSetView sourceAddressSet, AddressSetView destinationAddressSet,
|
AddressSetView sourceAddressSet, AddressSetView destinationAddressSet,
|
||||||
VTProgramCorrelatorFactory factory) throws CancelledException,
|
VTProgramCorrelatorFactory factory)
|
||||||
VTAssociationStatusException {
|
throws CancelledException, VTAssociationStatusException {
|
||||||
|
|
||||||
AddressSetView restrictedSourceAddresses =
|
AddressSetView restrictedSourceAddresses =
|
||||||
excludeAcceptedMatches(session, sourceAddressSet, true);
|
excludeAcceptedMatches(session, sourceAddressSet, true);
|
||||||
AddressSetView restrictedDestinationAddresses =
|
AddressSetView restrictedDestinationAddresses =
|
||||||
excludeAcceptedMatches(session, destinationAddressSet, false);
|
excludeAcceptedMatches(session, destinationAddressSet, false);
|
||||||
VTOptions options = factory.createDefaultOptions();
|
VTOptions options = factory.createDefaultOptions();
|
||||||
VTProgramCorrelator correlator =
|
VTProgramCorrelator correlator = factory.createCorrelator(serviceProvider, sourceProgram,
|
||||||
factory.createCorrelator(serviceProvider, sourceProgram, restrictedSourceAddresses,
|
restrictedSourceAddresses, destinationProgram, restrictedDestinationAddresses, options);
|
||||||
destinationProgram, restrictedDestinationAddresses, options);
|
|
||||||
|
|
||||||
VTMatchSet results = correlator.correlate(session, monitor);
|
VTMatchSet results = correlator.correlate(session, monitor);
|
||||||
applyMatches(manager, results.getMatches());
|
applyMatches(manager, results.getMatches());
|
||||||
|
|
|
@ -29,6 +29,20 @@ import ghidra.util.Msg;
|
||||||
|
|
||||||
public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
|
public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
|
||||||
|
|
||||||
|
private Program p1;
|
||||||
|
private Program p2;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup(boolean success) {
|
||||||
|
if (p1 != null) {
|
||||||
|
p1.release(this);
|
||||||
|
}
|
||||||
|
if (p2 != null) {
|
||||||
|
p2.release(this);
|
||||||
|
}
|
||||||
|
super.cleanup(success);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void run() throws Exception {
|
protected void run() throws Exception {
|
||||||
Project project = state.getProject();
|
Project project = state.getProject();
|
||||||
|
@ -39,13 +53,20 @@ public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
|
||||||
// Prompt the user to load the two programs that will be analyzed.
|
// Prompt the user to load the two programs that will be analyzed.
|
||||||
// This will only allow you to select programs from the currently-open
|
// This will only allow you to select programs from the currently-open
|
||||||
// project in Ghidra, so import them if you haven't already.
|
// project in Ghidra, so import them if you haven't already.
|
||||||
Program p1 = askProgram("Program1_Version1");
|
p1 = askProgram("Program1_Version1");
|
||||||
Program p2 = askProgram("Program1_Version2");
|
if (p1 == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
p2 = askProgram("Program1_Version2");
|
||||||
|
if (p2 == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the selected programs are not open and locked by Ghidra. If so,
|
// Make sure the selected programs are not open and locked by Ghidra. If so,
|
||||||
// warn the user.
|
// warn the user.
|
||||||
if (areProgramsLocked(p1, p2)) {
|
if (areProgramsLocked(p1, p2)) {
|
||||||
Msg.showError(this, null, "Program is locked!", "One of the programs you selected is locked by Ghidra. Please correct and try again.");
|
Msg.showError(this, null, "Program is locked!",
|
||||||
|
"One of the programs you selected is locked by Ghidra. Please correct and try again.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ public class AutoVersionTrackingTask extends Task {
|
||||||
private static int NUM_CORRELATORS = 8;
|
private static int NUM_CORRELATORS = 8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for AutoVersionTrackingCommand
|
* Constructor for a modal/blocking AutoVersionTrackingTask
|
||||||
*
|
*
|
||||||
* @param controller The Version Tracking controller for this session containing option and
|
* @param controller The Version Tracking controller for this session containing option and
|
||||||
* tool information needed for this command.
|
* tool information needed for this command.
|
||||||
|
@ -483,8 +483,6 @@ public class AutoVersionTrackingTask extends Task {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// remove any matches that have identical source functions - if more than one
|
// remove any matches that have identical source functions - if more than one
|
||||||
// with exactly the same instructions and operands then cannot determine a unique match
|
// with exactly the same instructions and operands then cannot determine a unique match
|
||||||
Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor);
|
Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor);
|
||||||
|
|
|
@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String path = "/";
|
String path = "/";
|
||||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||||
BufferFile bf = null;
|
BufferFile bf = null;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
|
@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||||
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
|
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
String path = "/";
|
String path = "/";
|
||||||
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
|
String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
|
||||||
FolderItem item = userFilesystem.getItem(path, name);
|
FolderItem item = userFilesystem.getItem(path, name);
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
item.delete(-1, null);
|
item.delete(-1, null);
|
||||||
|
@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||||
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
|
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
String path = "/";
|
String path = "/";
|
||||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||||
FolderItem item = userfs.getItem(path, name);
|
FolderItem item = userfs.getItem(path, name);
|
||||||
if (item == null || !(item instanceof DatabaseItem) ||
|
if (item == null || !(item instanceof DatabaseItem) ||
|
||||||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {
|
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {
|
||||||
|
|
|
@ -16,12 +16,14 @@
|
||||||
package ghidra.framework.data;
|
package ghidra.framework.data;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import generic.timer.GhidraSwinglessTimer;
|
import generic.timer.GhidraSwinglessTimer;
|
||||||
import ghidra.framework.client.*;
|
import ghidra.framework.client.*;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.remote.User;
|
import ghidra.framework.remote.User;
|
||||||
import ghidra.framework.store.*;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.framework.store.FileSystem;
|
import ghidra.framework.store.FileSystem;
|
||||||
|
@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
|
||||||
/**
|
/**
|
||||||
* Helper class to manage files within a project.
|
* Helper class to manage files within a project.
|
||||||
*/
|
*/
|
||||||
public class ProjectFileManager implements ProjectData {
|
public class DefaultProjectData implements ProjectData {
|
||||||
|
|
||||||
/**Name of folder that stores user's data*/
|
/**
|
||||||
|
* {@code fileTrackingMap} is used to identify DefaultProjectData instances which are
|
||||||
|
* tracking specific DomainObjectAdapter instances which are open.
|
||||||
|
*/
|
||||||
|
private static Map<DomainObjectAdapter, DefaultProjectData> fileTrackingMap =
|
||||||
|
Collections.synchronizedMap(new IdentityHashMap<>());
|
||||||
|
|
||||||
|
// Names of folders that stores project data
|
||||||
public static final String MANGLED_DATA_FOLDER_NAME = "data";
|
public static final String MANGLED_DATA_FOLDER_NAME = "data";
|
||||||
public static final String INDEXED_DATA_FOLDER_NAME = "idata";
|
public static final String INDEXED_DATA_FOLDER_NAME = "idata";
|
||||||
public static final String USER_FOLDER_NAME = "user";
|
public static final String USER_FOLDER_NAME = "user";
|
||||||
|
@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
|
||||||
|
|
||||||
private RootGhidraFolderData rootFolderData;
|
private RootGhidraFolderData rootFolderData;
|
||||||
|
|
||||||
private Map<String, DomainObjectAdapter> openDomainObjects =
|
private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
|
||||||
new HashMap<>();
|
|
||||||
|
|
||||||
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
|
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
|
||||||
|
|
||||||
private ProjectLock projectLock;
|
private ProjectLock projectLock;
|
||||||
private String owner;
|
private String owner;
|
||||||
|
|
||||||
|
private int inUseCount = 0; // open file count plus active merge sessions
|
||||||
|
private boolean closed = false;
|
||||||
|
private boolean disposed = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for existing projects.
|
* Constructor for existing projects.
|
||||||
* @param localStorageLocator the location of the project
|
* @param localStorageLocator the location of the project
|
||||||
|
@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
* write lock (i.e., project in-use)
|
* write lock (i.e., project in-use)
|
||||||
* @throws FileNotFoundException if project directory not found
|
* @throws FileNotFoundException if project directory not found
|
||||||
*/
|
*/
|
||||||
public ProjectFileManager(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
||||||
boolean resetOwner) throws NotOwnerException, IOException, LockException {
|
boolean resetOwner) throws NotOwnerException, IOException, LockException {
|
||||||
|
|
||||||
this.localStorageLocator = localStorageLocator;
|
this.localStorageLocator = localStorageLocator;
|
||||||
|
@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
|
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
|
||||||
* lock (i.e., project in-use)
|
* lock (i.e., project in-use)
|
||||||
*/
|
*/
|
||||||
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||||
boolean isInWritableProject) throws IOException, LockException {
|
boolean isInWritableProject) throws IOException, LockException {
|
||||||
this.localStorageLocator = localStorageLocator;
|
this.localStorageLocator = localStorageLocator;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
* @param versionedFileSystem an existing versioned file-system
|
* @param versionedFileSystem an existing versioned file-system
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.localStorageLocator = new ProjectLocator(null, "Test");
|
this.localStorageLocator = new ProjectLocator(null, "Test");
|
||||||
owner = SystemUtilities.getUserName();
|
owner = SystemUtilities.getUserName();
|
||||||
|
@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the owner of the project that is associated with this
|
* Returns the owner of the project that is associated with this
|
||||||
* ProjectFileManager. A value of null indicates an old multiuser
|
* DefaultProjectData. A value of null indicates an old multiuser
|
||||||
* project.
|
* project.
|
||||||
* @return the owner of the project
|
* @return the owner of the project
|
||||||
*/
|
*/
|
||||||
|
@ -731,8 +743,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
||||||
TaskMonitor monitor)
|
TaskMonitor monitor) throws IOException, CancelledException {
|
||||||
throws IOException, CancelledException {
|
|
||||||
|
|
||||||
newRepository.connect();
|
newRepository.connect();
|
||||||
if (!newRepository.isConnected()) {
|
if (!newRepository.isConnected()) {
|
||||||
|
@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
|
||||||
long checkoutId = item.getCheckoutId();
|
long checkoutId = item.getCheckoutId();
|
||||||
int checkoutVersion = item.getCheckoutVersion();
|
int checkoutVersion = item.getCheckoutVersion();
|
||||||
|
|
||||||
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
|
ItemCheckoutStatus otherCheckoutStatus =
|
||||||
df.getParent().getPathname(), df.getName(), checkoutId);
|
newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
|
||||||
|
|
||||||
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
|
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
* @throws CancelledException if task cancelled
|
* @throws CancelledException if task cancelled
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
|
@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
* @throws CancelledException if task cancelled
|
* @throws CancelledException if task cancelled
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
List<DomainFile> list = new ArrayList<>();
|
List<DomainFile> list = new ArrayList<>();
|
||||||
|
@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
|
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
|
||||||
TaskMonitor monitor)
|
TaskMonitor monitor) throws IOException, CancelledException {
|
||||||
throws IOException, CancelledException {
|
|
||||||
|
|
||||||
for (String name : fileSystem.getItemNames(folderPath)) {
|
for (String name : fileSystem.getItemNames(folderPath)) {
|
||||||
monitor.checkCancelled();
|
monitor.checkCancelled();
|
||||||
|
@ -902,6 +914,30 @@ public class ProjectFileManager implements ProjectData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getSharedProjectURL() {
|
||||||
|
URL projectURL = localStorageLocator.getURL();
|
||||||
|
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||||
|
if (repository == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// NOTE: only supports ghidra protocol without extension protocol.
|
||||||
|
// Assumes any extension protocol use would be reflected in ProjectLocator URL.
|
||||||
|
ServerInfo serverInfo = repository.getServerInfo();
|
||||||
|
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
||||||
|
repository.getName());
|
||||||
|
}
|
||||||
|
return projectURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getLocalProjectURL() {
|
||||||
|
if (!localStorageLocator.isTransient()) {
|
||||||
|
return localStorageLocator.getURL();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the standard user data filename associated with the specified file ID.
|
* Returns the standard user data filename associated with the specified file ID.
|
||||||
* @param associatedFileID the file id
|
* @param associatedFileID the file id
|
||||||
|
@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
}
|
}
|
||||||
|
|
||||||
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
|
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
|
||||||
synchronized (ProjectFileManager.this) {
|
synchronized (DefaultProjectData.this) {
|
||||||
startReconcileUserDataFiles();
|
startReconcileUserDataFiles();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
|
||||||
return projectDir;
|
return projectDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isClosed() {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDisposed() {
|
||||||
|
return disposed;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (!closed) {
|
||||||
|
Msg.debug(this, "Closing ProjectData: " + projectDir);
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
if (inUseCount != 0) {
|
||||||
|
return; // delay dispose
|
||||||
|
}
|
||||||
|
}
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
private synchronized void incrementInUseCount() {
|
||||||
|
++inUseCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decrementInUseCount() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (inUseCount <= 0) {
|
||||||
|
Msg.error(this, "DefaultProjectData in-use tracking is out-of-sync: " + projectDir);
|
||||||
|
}
|
||||||
|
if (--inUseCount > 0 || !closed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately dispose this project data store instance. If this project has an associated
|
||||||
|
* {@link RepositoryAdapter} it will be disconnected as well. This method should generally not
|
||||||
|
* be used directly when there may be open {@link DomainObject} instances which may rely
|
||||||
|
* on an associated server connection. The {@link #clone()} method should be used when
|
||||||
|
* open {@link DomainObject} instances may exist and should be allowed to persist until
|
||||||
|
* they are closed.
|
||||||
|
*/
|
||||||
|
protected void dispose() {
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
if (disposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Msg.debug(this, "Disposing ProjectData: " + projectDir);
|
||||||
|
|
||||||
|
closed = true;
|
||||||
|
disposed = true;
|
||||||
|
|
||||||
if (userDataReconcileTimer != null) {
|
if (userDataReconcileTimer != null) {
|
||||||
userDataReconcileTimer.stop();
|
userDataReconcileTimer.stop();
|
||||||
}
|
}
|
||||||
|
@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
|
||||||
/**
|
/**
|
||||||
* Returns the open domain object (opened for update) for the specified path.
|
* Returns the open domain object (opened for update) for the specified path.
|
||||||
* @param pathname the path name
|
* @param pathname the path name
|
||||||
* @return the domain object
|
* @return the domain object or null if not open
|
||||||
*/
|
*/
|
||||||
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
|
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
|
||||||
return openDomainObjects.get(pathname);
|
return openDomainObjects.get(pathname);
|
||||||
|
@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
|
||||||
public TaskMonitor getProjectDisposalMonitor() {
|
public TaskMonitor getProjectDisposalMonitor() {
|
||||||
return projectDisposalMonitor;
|
return projectDisposalMonitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals the start of a complex merge operation.
|
||||||
|
* The {@link #mergeEnded()} must be invoked after this method invocation when the
|
||||||
|
* merge operation has completed.
|
||||||
|
*/
|
||||||
|
void mergeStarted() {
|
||||||
|
incrementInUseCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals the completion of a complex merge operation (see {@link #mergeStarted()}).
|
||||||
|
*/
|
||||||
|
void mergeEnded() {
|
||||||
|
decrementInUseCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that a <b>non-link</b> file has been opened as the specified
|
||||||
|
* {@link DomainObjectAdapter doa} from this project data store and should be
|
||||||
|
* tracked. This will delay disposal of this object until the specified domain object is
|
||||||
|
* either closed or saved to a different project store (i.e., hand-off operation).
|
||||||
|
* It is important that this method not be invoked when opening a link-file
|
||||||
|
* since it is the referenced file being opened that must be tracked and not the
|
||||||
|
* opening of the link-file itself.
|
||||||
|
* @param doa domain object
|
||||||
|
*/
|
||||||
|
void trackDomainFileInUse(DomainObjectAdapter doa) {
|
||||||
|
DefaultProjectData projectData = fileTrackingMap.put(doa, this);
|
||||||
|
if (projectData == this) {
|
||||||
|
return; // no change in associated project
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectData != null) {
|
||||||
|
projectData.decrementInUseCount();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
doa.addCloseListener(dobj -> domainObjectClosed(dobj));
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementInUseCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void domainObjectClosed(DomainObject dobj) {
|
||||||
|
|
||||||
|
DefaultProjectData projectData = fileTrackingMap.remove(dobj);
|
||||||
|
if (projectData != null) {
|
||||||
|
projectData.decrementInUseCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -29,10 +28,10 @@ import java.util.HashMap;
|
||||||
*/
|
*/
|
||||||
class DomainFileIndex implements DomainFolderChangeListener {
|
class DomainFileIndex implements DomainFolderChangeListener {
|
||||||
|
|
||||||
private ProjectFileManager projectData;
|
private DefaultProjectData projectData;
|
||||||
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
|
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
|
||||||
|
|
||||||
DomainFileIndex(ProjectFileManager projectData) {
|
DomainFileIndex(DefaultProjectData projectData) {
|
||||||
this.projectData = projectData;
|
this.projectData = projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,11 @@ import java.util.*;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import ghidra.framework.client.*;
|
import ghidra.framework.client.*;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.remote.RepositoryItem;
|
import ghidra.framework.remote.RepositoryItem;
|
||||||
import ghidra.framework.store.ItemCheckoutStatus;
|
import ghidra.framework.store.*;
|
||||||
import ghidra.framework.store.Version;
|
|
||||||
import ghidra.framework.store.db.PackedDatabase;
|
import ghidra.framework.store.db.PackedDatabase;
|
||||||
import ghidra.util.InvalidNameException;
|
import ghidra.util.InvalidNameException;
|
||||||
import ghidra.util.ReadOnlyException;
|
import ghidra.util.ReadOnlyException;
|
||||||
|
@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
|
||||||
|
|
||||||
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
|
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
|
||||||
try {
|
try {
|
||||||
String spec = getPathname().substring(1); // remove leading '/'
|
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||||
if (!StringUtils.isEmpty(ref)) {
|
String urlStr = sharedProjectURL.toExternalForm();
|
||||||
spec += "#" + ref;
|
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
|
||||||
|
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||||
}
|
}
|
||||||
return new URL(sharedProjectURL, spec);
|
urlStr += getPathname();
|
||||||
|
return new URL(urlStr);
|
||||||
}
|
}
|
||||||
catch (MalformedURLException e) {
|
catch (MalformedURLException e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME);
|
String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
|
||||||
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME);
|
String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
|
||||||
if (serverName == null || repoName == null) {
|
if (serverName == null || repoName == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int port = Integer.parseInt(properties.getProperty(ProjectFileManager.PORT_NUMBER, "0"));
|
int port = Integer.parseInt(properties.getProperty(DefaultProjectData.PORT_NUMBER, "0"));
|
||||||
|
|
||||||
if (!ClientUtil.isConnected(serverName, port)) {
|
if (!ClientUtil.isConnected(serverName, port)) {
|
||||||
return null; // avoid initiating a server connection.
|
return null; // avoid initiating a server connection.
|
||||||
|
@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
|
||||||
return getSharedFileURL(projectURL, ref);
|
return getSharedFileURL(projectURL, ref);
|
||||||
}
|
}
|
||||||
Properties properties =
|
Properties properties =
|
||||||
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
|
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
|
||||||
return getSharedFileURL(properties, ref);
|
return getSharedFileURL(properties, ref);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
||||||
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
|
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
|
||||||
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
|
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
|
||||||
private volatile boolean eventsEnabled = true;
|
private volatile boolean eventsEnabled = true;
|
||||||
private Set<DomainObjectClosedListener> closeListeners =
|
private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
|
||||||
new CopyOnWriteArraySet<DomainObjectClosedListener>();
|
|
||||||
|
|
||||||
private ArrayList<Object> consumers;
|
private ArrayList<Object> consumers;
|
||||||
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
|
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
|
||||||
|
@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
||||||
|
|
||||||
private void notifyCloseListeners() {
|
private void notifyCloseListeners() {
|
||||||
for (DomainObjectClosedListener listener : closeListeners) {
|
for (DomainObjectClosedListener listener : closeListeners) {
|
||||||
listener.domainObjectClosed();
|
listener.domainObjectClosed(this);
|
||||||
}
|
}
|
||||||
closeListeners.clear();
|
closeListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
|
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
|
||||||
|
|
||||||
protected ProjectFileManager fileManager;
|
protected DefaultProjectData projectData;
|
||||||
|
|
||||||
private LocalFileSystem fileSystem;
|
private LocalFileSystem fileSystem;
|
||||||
private DomainFolderChangeListener listener;
|
private DomainFolderChangeListener listener;
|
||||||
|
@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
this.fileManager = parent.getProjectFileManager();
|
this.projectData = parent.getProjectData();
|
||||||
this.fileSystem = parent.getLocalFileSystem();
|
this.fileSystem = parent.getLocalFileSystem();
|
||||||
this.listener = parent.getChangeListener();
|
this.listener = parent.getChangeListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalFileSystem getUserFileSystem() {
|
public LocalFileSystem getUserFileSystem() {
|
||||||
return fileManager.getUserFileSystem();
|
return projectData.getUserFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
|
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
|
||||||
|
@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
void clearDomainObj() {
|
void clearDomainObj() {
|
||||||
String path = getPathname();
|
String path = getPathname();
|
||||||
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
|
DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
|
||||||
if (doa != null && fileManager.clearDomainObject(getPathname())) {
|
if (doa != null && projectData.clearDomainObject(getPathname())) {
|
||||||
listener.domainFileObjectClosed(this, doa);
|
listener.domainFileObjectClosed(this, doa);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectLocator getProjectLocator() {
|
public ProjectLocator getProjectLocator() {
|
||||||
return fileManager.getProjectLocator();
|
return projectData.getProjectLocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DomainObject getOpenedDomainObject(Object consumer) {
|
public DomainObject getOpenedDomainObject(Object consumer) {
|
||||||
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
|
DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
|
||||||
if (domainObj != null) {
|
if (domainObj != null) {
|
||||||
if (!domainObj.addConsumer(consumer)) {
|
if (!domainObj.addConsumer(consumer)) {
|
||||||
fileManager.clearDomainObject(getPathname());
|
projectData.clearDomainObject(getPathname());
|
||||||
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
|
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(TaskMonitor monitor) throws IOException, CancelledException {
|
public void save(TaskMonitor monitor) throws IOException, CancelledException {
|
||||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||||
if (dobj == null) {
|
if (dobj == null) {
|
||||||
throw new AssertException("Cannot save, domainObj not open");
|
throw new AssertException("Cannot save, domainObj not open");
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canSave() {
|
public boolean canSave() {
|
||||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||||
if (dobj == null) {
|
if (dobj == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<?> getConsumers() {
|
public ArrayList<?> getConsumers() {
|
||||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||||
if (dobj == null) {
|
if (dobj == null) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isChanged() {
|
public boolean isChanged() {
|
||||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||||
return dobj != null && dobj.isChanged();
|
return dobj != null && dobj.isChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpen() {
|
public boolean isOpen() {
|
||||||
return fileManager.getOpenedDomainObject(getPathname()) != null;
|
return projectData.getOpenedDomainObject(getPathname()) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GhidraFile other = (GhidraFile) obj;
|
GhidraFile other = (GhidraFile) obj;
|
||||||
if (fileManager != other.fileManager) {
|
if (projectData != other.projectData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getPathname().equals(other.getPathname());
|
return getPathname().equals(other.getPathname());
|
||||||
|
@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator();
|
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||||
if (projectLocator.isTransient()) {
|
if (projectLocator.isTransient()) {
|
||||||
return fileManager.getProjectLocator().getName() + getPathname();
|
return projectLocator.getName() + getPathname();
|
||||||
}
|
}
|
||||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
return projectLocator.getName() + ":" + getPathname();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,6 @@ import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.framework.client.RepositoryAdapter;
|
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.store.FileSystem;
|
import ghidra.framework.store.FileSystem;
|
||||||
|
@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class GhidraFolder implements DomainFolder {
|
public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
private ProjectFileManager fileManager;
|
private DefaultProjectData projectData;
|
||||||
private LocalFileSystem fileSystem;
|
private LocalFileSystem fileSystem;
|
||||||
private FileSystem versionedFileSystem;
|
private FileSystem versionedFileSystem;
|
||||||
private DomainFolderChangeListener listener;
|
private DomainFolderChangeListener listener;
|
||||||
|
@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
|
||||||
private GhidraFolder parent;
|
private GhidraFolder parent;
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||||
this.fileManager = fileManager;
|
this.projectData = projectData;
|
||||||
this.fileSystem = fileManager.getLocalFileSystem();
|
this.fileSystem = projectData.getLocalFileSystem();
|
||||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.name = FileSystem.SEPARATOR;
|
this.name = FileSystem.SEPARATOR;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
this.fileManager = parent.getProjectFileManager();
|
this.projectData = parent.getProjectData();
|
||||||
this.fileSystem = parent.getLocalFileSystem();
|
this.fileSystem = parent.getLocalFileSystem();
|
||||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||||
this.listener = parent.getChangeListener();
|
this.listener = parent.getChangeListener();
|
||||||
|
@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem getUserFileSystem() {
|
LocalFileSystem getUserFileSystem() {
|
||||||
return fileManager.getUserFileSystem();
|
return projectData.getUserFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainFolderChangeListener getChangeListener() {
|
DomainFolderChangeListener getChangeListener() {
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectFileManager getProjectFileManager() {
|
|
||||||
return fileManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
|
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
|
||||||
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
|
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
|
||||||
if (fileData == null) {
|
if (fileData == null) {
|
||||||
|
@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
GhidraFolderData getFolderData() throws FileNotFoundException {
|
GhidraFolderData getFolderData() throws FileNotFoundException {
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return fileManager.getRootFolderData();
|
return projectData.getRootFolderData();
|
||||||
}
|
}
|
||||||
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
|
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
|
||||||
if (folderData == null) {
|
if (folderData == null) {
|
||||||
|
@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
private GhidraFolderData createFolderData(String folderName) throws IOException {
|
private GhidraFolderData createFolderData(String folderName) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
GhidraFolderData parentData =
|
GhidraFolderData parentData =
|
||||||
parent == null ? fileManager.getRootFolderData() : createFolderData();
|
parent == null ? projectData.getRootFolderData() : createFolderData();
|
||||||
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
|
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
|
||||||
if (folderData == null) {
|
if (folderData == null) {
|
||||||
try {
|
try {
|
||||||
|
@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private GhidraFolderData createFolderData() throws IOException {
|
private GhidraFolderData createFolderData() throws IOException {
|
||||||
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
|
GhidraFolderData rootFolderData = projectData.getRootFolderData();
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return rootFolderData;
|
return rootFolderData;
|
||||||
}
|
}
|
||||||
|
@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectLocator getProjectLocator() {
|
public ProjectLocator getProjectLocator() {
|
||||||
return fileManager.getProjectLocator();
|
return projectData.getProjectLocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectFileManager getProjectData() {
|
public DefaultProjectData getProjectData() {
|
||||||
return fileManager;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPathname(String childName) {
|
String getPathname(String childName) {
|
||||||
|
@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getSharedProjectURL() {
|
public URL getSharedProjectURL() {
|
||||||
ProjectLocator projectLocator = getProjectLocator();
|
URL projectURL = projectData.getSharedProjectURL();
|
||||||
URL projectURL = projectLocator.getURL();
|
if (projectURL == null) {
|
||||||
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
|
return null;
|
||||||
RepositoryAdapter repository = fileManager.getRepository();
|
|
||||||
if (repository == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// NOTE: only supports ghidra protocol without extension protocol.
|
|
||||||
// Assumes any extension protocol use would be reflected in projectLocator URL.
|
|
||||||
ServerInfo serverInfo = repository.getServerInfo();
|
|
||||||
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
|
||||||
repository.getName());
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||||
|
@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getLocalProjectURL() {
|
public URL getLocalProjectURL() {
|
||||||
ProjectLocator projectLocator = parent.getProjectLocator();
|
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||||
if (!projectLocator.isTransient()) {
|
if (!projectLocator.isTransient()) {
|
||||||
return GhidraURL.makeURL(projectLocator, getPathname(), null);
|
return GhidraURL.makeURL(projectLocator, getPathname(), null);
|
||||||
}
|
}
|
||||||
|
@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInWritableProject() {
|
public boolean isInWritableProject() {
|
||||||
return !getProjectData().getLocalFileSystem().isReadOnly();
|
return !fileSystem.isReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
|
Msg.error(this, "file error for " + getPathname(fileName), e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
GhidraFolder other = (GhidraFolder) obj;
|
GhidraFolder other = (GhidraFolder) obj;
|
||||||
if (fileManager != other.fileManager) {
|
if (projectData != other.projectData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getPathname().equals(other.getPathname());
|
return getPathname().equals(other.getPathname());
|
||||||
|
@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||||
if (projectLocator.isTransient()) {
|
if (projectLocator.isTransient()) {
|
||||||
return fileManager.getProjectLocator().getName() + getPathname();
|
return projectData.getProjectLocator().getName() + getPathname();
|
||||||
}
|
}
|
||||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
return projectData.getProjectLocator().getName() + ":" + getPathname();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,25 @@ import ghidra.framework.model.*;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||||
import ghidra.framework.protocol.ghidra.TransientProjectData;
|
import ghidra.framework.protocol.ghidra.TransientProjectData;
|
||||||
import ghidra.framework.store.FileSystem;
|
import ghidra.framework.store.FileSystem;
|
||||||
|
import ghidra.framework.store.FolderNotEmptyException;
|
||||||
import ghidra.framework.store.local.LocalFileSystem;
|
import ghidra.framework.store.local.LocalFileSystem;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
import ghidra.util.exception.*;
|
import ghidra.util.exception.*;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link GhidraFolderData} provides the managed object which represents a project folder that
|
||||||
|
* corresponds to matched folder paths across both a versioned and private
|
||||||
|
* filesystem and viewed as a single folder at the project level. This class closely mirrors the
|
||||||
|
* {@link DomainFolder} interface and is used by the {@link GhidraFolder} implementation; both of which
|
||||||
|
* represent immutable folder references. Changes made to this folder's name or path are not reflected
|
||||||
|
* in old {@link DomainFolder} instances and must be re-instantiated following such a change.
|
||||||
|
* Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an
|
||||||
|
* appropriate change listener to properly discard/reacquire such instances.
|
||||||
|
*/
|
||||||
class GhidraFolderData {
|
class GhidraFolderData {
|
||||||
|
|
||||||
private ProjectFileManager fileManager;
|
private DefaultProjectData projectData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Folder change listener - change events only sent if folder is visited
|
* Folder change listener - change events only sent if folder is visited
|
||||||
|
@ -59,17 +70,24 @@ class GhidraFolderData {
|
||||||
private boolean versionedFolderExists;
|
private boolean versionedFolderExists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General constructor reserved for root folder use only
|
* General constructor reserved for root folder instantiation
|
||||||
* @param fileManager
|
* @param projectData associated project data instance
|
||||||
* @param listener
|
* @param listener folder change listener
|
||||||
*/
|
*/
|
||||||
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||||
this.fileManager = fileManager;
|
this.projectData = projectData;
|
||||||
this.fileSystem = fileManager.getLocalFileSystem();
|
this.fileSystem = projectData.getLocalFileSystem();
|
||||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a folder instance with a specified name and a correpsonding parent folder
|
||||||
|
* @param parent parent folder
|
||||||
|
* @param name folder name
|
||||||
|
* @throws FileNotFoundException if folder not found or error occured while checking
|
||||||
|
* for its existance
|
||||||
|
*/
|
||||||
GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException {
|
GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException {
|
||||||
if (name == null || name.isEmpty()) {
|
if (name == null || name.isEmpty()) {
|
||||||
throw new FileNotFoundException("Bad folder name: blank or null");
|
throw new FileNotFoundException("Bad folder name: blank or null");
|
||||||
|
@ -77,7 +95,7 @@ class GhidraFolderData {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
this.fileManager = parent.getProjectFileManager();
|
this.projectData = parent.getProjectData();
|
||||||
this.fileSystem = parent.getLocalFileSystem();
|
this.fileSystem = parent.getLocalFileSystem();
|
||||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||||
this.listener = parent.getChangeListener();
|
this.listener = parent.getChangeListener();
|
||||||
|
@ -95,36 +113,59 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if folder has complete list of children
|
* @return true if folder has complete list of children
|
||||||
*/
|
*/
|
||||||
boolean visited() {
|
boolean visited() {
|
||||||
return visited;
|
return visited;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return local file system
|
||||||
|
*/
|
||||||
LocalFileSystem getLocalFileSystem() {
|
LocalFileSystem getLocalFileSystem() {
|
||||||
return fileSystem;
|
return fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return versioned file system
|
||||||
|
*/
|
||||||
FileSystem getVersionedFileSystem() {
|
FileSystem getVersionedFileSystem() {
|
||||||
return versionedFileSystem;
|
return versionedFileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return local user data file system
|
||||||
|
*/
|
||||||
LocalFileSystem getUserFileSystem() {
|
LocalFileSystem getUserFileSystem() {
|
||||||
return fileManager.getUserFileSystem();
|
return projectData.getUserFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return folder change listener
|
||||||
|
*/
|
||||||
DomainFolderChangeListener getChangeListener() {
|
DomainFolderChangeListener getChangeListener() {
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectFileManager getProjectFileManager() {
|
/**
|
||||||
return fileManager;
|
* @return project data instance
|
||||||
|
*/
|
||||||
|
DefaultProjectData getProjectData() {
|
||||||
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the project locator which identifies the system storage
|
||||||
|
* are for the local file system and other project related resources.
|
||||||
|
* @return local project locator
|
||||||
|
*/
|
||||||
ProjectLocator getProjectLocator() {
|
ProjectLocator getProjectLocator() {
|
||||||
return fileManager.getProjectLocator();
|
return projectData.getProjectLocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return this folder's parent folder or null if this is the root folder.
|
||||||
|
*/
|
||||||
GhidraFolderData getParentData() {
|
GhidraFolderData getParentData() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
@ -143,7 +184,7 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
|
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
|
||||||
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy);
|
return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
|
||||||
}
|
}
|
||||||
if (folderPath.length() == 0) {
|
if (folderPath.length() == 0) {
|
||||||
return this;
|
return this;
|
||||||
|
@ -168,10 +209,26 @@ class GhidraFolderData {
|
||||||
return folderData.getFolderPathData(nextPath, lazy);
|
return folderData.getFolderPathData(nextPath, lazy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this folder's name.
|
||||||
|
* @return the name
|
||||||
|
*/
|
||||||
String getName() {
|
String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name on this domain folder.
|
||||||
|
* @param newName domain folder name
|
||||||
|
* @return renamed domain file (the original DomainFolder object becomes invalid since it is
|
||||||
|
* immutable)
|
||||||
|
* @throws InvalidNameException if newName contains illegal characters
|
||||||
|
* @throws DuplicateFileException if a folder named newName
|
||||||
|
* already exists in this files domain folder.
|
||||||
|
* @throws FileInUseException if any file within this folder or its descendants is
|
||||||
|
* in-use / checked-out.
|
||||||
|
* @throws IOException thrown if an IO or access error occurs.
|
||||||
|
*/
|
||||||
GhidraFolder setName(String newName) throws InvalidNameException, IOException {
|
GhidraFolder setName(String newName) throws InvalidNameException, IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (parent == null || fileSystem.isReadOnly()) {
|
if (parent == null || fileSystem.isReadOnly()) {
|
||||||
|
@ -242,6 +299,10 @@ class GhidraFolderData {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full path name to this folder
|
||||||
|
* @return the path name
|
||||||
|
*/
|
||||||
String getPathname() {
|
String getPathname() {
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return FileSystem.SEPARATOR;
|
return FileSystem.SEPARATOR;
|
||||||
|
@ -254,6 +315,10 @@ class GhidraFolderData {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if this folder contains any sub-folders or domain files.
|
||||||
|
* @return true if this folder is empty.
|
||||||
|
*/
|
||||||
boolean isEmpty() {
|
boolean isEmpty() {
|
||||||
try {
|
try {
|
||||||
refresh(false, false, null); // visited will be true upon return
|
refresh(false, false, null); // visited will be true upon return
|
||||||
|
@ -266,6 +331,10 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of names for all files contained within this folder.
|
||||||
|
* @return list of file names
|
||||||
|
*/
|
||||||
List<String> getFileNames() {
|
List<String> getFileNames() {
|
||||||
try {
|
try {
|
||||||
refresh(false, false, null); // visited will be true upon return
|
refresh(false, false, null); // visited will be true upon return
|
||||||
|
@ -277,6 +346,10 @@ class GhidraFolderData {
|
||||||
return new ArrayList<>(fileList);
|
return new ArrayList<>(fileList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of names for all subfolders contained within this folder.
|
||||||
|
* @return list of file names
|
||||||
|
*/
|
||||||
List<String> getFolderNames() {
|
List<String> getFolderNames() {
|
||||||
try {
|
try {
|
||||||
refresh(false, false, null); // visited will be true upon return
|
refresh(false, false, null); // visited will be true upon return
|
||||||
|
@ -289,9 +362,10 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update file list/cache based upon rename of file.
|
* Update file list/cache based upon rename of a file.
|
||||||
* If this folder has been visited listener will be notified with rename
|
* If this folder has been visited the listener will be notified with rename
|
||||||
* @param oldName
|
* @param oldFileName file name prior to rename
|
||||||
|
* @param newFileName file name after rename
|
||||||
*/
|
*/
|
||||||
void fileRenamed(String oldFileName, String newFileName) {
|
void fileRenamed(String oldFileName, String newFileName) {
|
||||||
GhidraFileData fileData;
|
GhidraFileData fileData;
|
||||||
|
@ -314,6 +388,14 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update file list/cache based upon change of parent for a file.
|
||||||
|
* If this folder or the newParent has been visited the listener will be notified with add/move
|
||||||
|
* details.
|
||||||
|
* @param newParent new parent folder
|
||||||
|
* @param oldFileName file name prior to move
|
||||||
|
* @param newFileName file name after move
|
||||||
|
*/
|
||||||
void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) {
|
void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) {
|
||||||
GhidraFileData fileData;
|
GhidraFileData fileData;
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -340,7 +422,7 @@ class GhidraFolderData {
|
||||||
* underlying local or versioned file. If this folder has been visited an appropriate
|
* underlying local or versioned file. If this folder has been visited an appropriate
|
||||||
* add/remove/change notification will be provided to the listener.
|
* add/remove/change notification will be provided to the listener.
|
||||||
* NOTE: Move and Rename situations are not handled
|
* NOTE: Move and Rename situations are not handled
|
||||||
* @param fileName
|
* @param fileName name of file which has changed
|
||||||
*/
|
*/
|
||||||
void fileChanged(String fileName) {
|
void fileChanged(String fileName) {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -389,7 +471,8 @@ class GhidraFolderData {
|
||||||
* visited an appropriate add/remove/change notification will be provided to the listener.
|
* visited an appropriate add/remove/change notification will be provided to the listener.
|
||||||
* NOTE: Care should be taken using this method as all sub-folder cache data may be disposed!
|
* NOTE: Care should be taken using this method as all sub-folder cache data may be disposed!
|
||||||
* NOTE: Move and Rename situations are not handled
|
* NOTE: Move and Rename situations are not handled
|
||||||
* @param folderName
|
* @param folderName name of folder which has changed
|
||||||
|
* @throws IOException if an IO error occurs during associated refresh
|
||||||
*/
|
*/
|
||||||
void folderChanged(String folderName) throws IOException {
|
void folderChanged(String folderName) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -404,7 +487,7 @@ class GhidraFolderData {
|
||||||
if (folderData.versionedFolderExists || folderData.folderExists) {
|
if (folderData.versionedFolderExists || folderData.folderExists) {
|
||||||
// preserve subfolder data
|
// preserve subfolder data
|
||||||
if (folderData.visited) {
|
if (folderData.visited) {
|
||||||
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor());
|
folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -429,7 +512,7 @@ class GhidraFolderData {
|
||||||
/**
|
/**
|
||||||
* Remove and dispose specified subfolder data and notify listener of removal
|
* Remove and dispose specified subfolder data and notify listener of removal
|
||||||
* if this folder has been visited
|
* if this folder has been visited
|
||||||
* @param folderName
|
* @param folderName name of folder which was removed
|
||||||
*/
|
*/
|
||||||
void folderRemoved(String folderName) {
|
void folderRemoved(String folderName) {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -443,6 +526,9 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes the cached data for this folder and all of its children recursively.
|
||||||
|
*/
|
||||||
void dispose() {
|
void dispose() {
|
||||||
visited = false;
|
visited = false;
|
||||||
folderList.clear();
|
folderList.clear();
|
||||||
|
@ -458,7 +544,7 @@ class GhidraFolderData {
|
||||||
// NOTE: clearing the following can cause issues since there may be some residual
|
// NOTE: clearing the following can cause issues since there may be some residual
|
||||||
// activity/use which will get a NPE
|
// activity/use which will get a NPE
|
||||||
// parent = null;
|
// parent = null;
|
||||||
// fileManager = null;
|
// projectData = null;
|
||||||
// listener = null;
|
// listener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +562,7 @@ class GhidraFolderData {
|
||||||
* Refresh set of sub-folder names and identify added/removed folders.
|
* Refresh set of sub-folder names and identify added/removed folders.
|
||||||
* @param recursive recurse into visited subfolders if true
|
* @param recursive recurse into visited subfolders if true
|
||||||
* @param monitor recursion task monitor - break from recursion if cancelled
|
* @param monitor recursion task monitor - break from recursion if cancelled
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs during the refresh
|
||||||
*/
|
*/
|
||||||
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
|
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
|
||||||
|
|
||||||
|
@ -653,7 +739,7 @@ class GhidraFolderData {
|
||||||
* of visited state, if false refresh is lazy and will not be
|
* of visited state, if false refresh is lazy and will not be
|
||||||
* performed if a previous refresh set the visited state.
|
* performed if a previous refresh set the visited state.
|
||||||
* @param monitor recursion task monitor - break from recursion if cancelled
|
* @param monitor recursion task monitor - break from recursion if cancelled
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs during the refresh
|
||||||
*/
|
*/
|
||||||
void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException {
|
void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -699,11 +785,11 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for existence of subfolder. If this folder visited, rely on folderList
|
* Check for existence of subfolder. If this folder has previously been visited,
|
||||||
* @param fileName
|
* rely on the cached folderList.
|
||||||
* @param doRealCheck if true do not rely on fileList
|
* @param folderName name of folder to look for
|
||||||
* @return
|
* @return true if folder exists, else false
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs when checking for folder's existance.
|
||||||
*/
|
*/
|
||||||
boolean containsFolder(String folderName) throws IOException {
|
boolean containsFolder(String folderName) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -720,8 +806,8 @@ class GhidraFolderData {
|
||||||
/**
|
/**
|
||||||
* Create and add new subfolder data object to cache. Data will not be created
|
* Create and add new subfolder data object to cache. Data will not be created
|
||||||
* if folder does not exist or an IOException occurs.
|
* if folder does not exist or an IOException occurs.
|
||||||
* @param folderName
|
* @param folderName name of folder to be added
|
||||||
* @return folder data or null
|
* @return folder data or null if folder does not exist
|
||||||
*/
|
*/
|
||||||
private GhidraFolderData addFolderData(String folderName) {
|
private GhidraFolderData addFolderData(String folderName) {
|
||||||
GhidraFolderData folderData = folderDataCache.get(folderName);
|
GhidraFolderData folderData = folderDataCache.get(folderName);
|
||||||
|
@ -739,7 +825,7 @@ class GhidraFolderData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get folder data for child folder specified by folderName
|
* Get folder data for child folder specified by folderName
|
||||||
* @param folderName
|
* @param folderName name of folder
|
||||||
* @param lazy if true folder will not be searched for if not already discovered - in
|
* @param lazy if true folder will not be searched for if not already discovered - in
|
||||||
* this case null will be returned
|
* this case null will be returned
|
||||||
* @return folder data or null if not found or lazy=true and not yet discovered
|
* @return folder data or null if not found or lazy=true and not yet discovered
|
||||||
|
@ -763,10 +849,10 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for existence of file. If folder visited, rely on fileDataCache
|
* Check for existence of file. If folder previously visited, rely on fileDataCache
|
||||||
* @param fileName the name of the file to check for
|
* @param fileName the name of the file to look for
|
||||||
* @return true if this folder contains the fileName
|
* @return true if this folder contains the fileName, else false
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs while checking for file existance
|
||||||
*/
|
*/
|
||||||
public boolean containsFile(String fileName) throws IOException {
|
public boolean containsFile(String fileName) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -783,9 +869,9 @@ class GhidraFolderData {
|
||||||
/**
|
/**
|
||||||
* Create and add new file data object to cache. Data will not be created
|
* Create and add new file data object to cache. Data will not be created
|
||||||
* if file does not exist or an IOException occurs.
|
* if file does not exist or an IOException occurs.
|
||||||
* @param fileName
|
* @param fileName name of file
|
||||||
* @return file data or null
|
* @return file data or null if not found
|
||||||
* @throws IOException
|
* @throws IOException if an IO error occurs while checking for file existance
|
||||||
*/
|
*/
|
||||||
private GhidraFileData addFileData(String fileName) throws IOException {
|
private GhidraFileData addFileData(String fileName) throws IOException {
|
||||||
GhidraFileData fileData = fileDataCache.get(fileName);
|
GhidraFileData fileData = fileDataCache.get(fileName);
|
||||||
|
@ -793,7 +879,7 @@ class GhidraFolderData {
|
||||||
try {
|
try {
|
||||||
fileData = new GhidraFileData(this, fileName);
|
fileData = new GhidraFileData(this, fileName);
|
||||||
fileDataCache.put(fileName, fileData);
|
fileDataCache.put(fileName, fileData);
|
||||||
fileManager.updateFileIndex(fileData);
|
projectData.updateFileIndex(fileData);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException e) {
|
||||||
// ignore
|
// ignore
|
||||||
|
@ -804,10 +890,11 @@ class GhidraFolderData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get file data for child specified by fileName
|
* Get file data for child specified by fileName
|
||||||
* @param fileName
|
* @param fileName name of file
|
||||||
* @param lazy if true file will not be searched for if not already discovered - in
|
* @param lazy if true file will not be searched for if not already discovered - in
|
||||||
* this case null will be returned
|
* this case null will be returned
|
||||||
* @return file data or null if not found or lazy=true and not yet discovered
|
* @return file data or null if not found or lazy=true and not yet discovered
|
||||||
|
* @throws IOException if an IO error occurs while checking for file existance
|
||||||
*/
|
*/
|
||||||
GhidraFileData getFileData(String fileName, boolean lazy) throws IOException {
|
GhidraFileData getFileData(String fileName, boolean lazy) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -822,58 +909,11 @@ class GhidraFolderData {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// // TODO: Examine!
|
/**
|
||||||
// private void removeFolderX(String folderName) {
|
* Get the domain file in this folder with the given fileName.
|
||||||
// folderList.remove(folderName);
|
* @param fileName name of file in this folder to retrieve
|
||||||
// folderDataCache.remove(folderName);
|
* @return domain file or null if there is no file in this folder with the given name.
|
||||||
// listener.domainFolderRemoved(getDomainFolder(), folderName);
|
*/
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // TODO: Examine!
|
|
||||||
// void removeFileX(String fileName) {
|
|
||||||
// fileList.remove(fileName);
|
|
||||||
// GhidraFileV2Data fileData = fileDataCache.remove(fileName);
|
|
||||||
// if (fileData != null) {
|
|
||||||
// fileData.dispose();
|
|
||||||
// }
|
|
||||||
//// TODO: May need to eliminate presence of fileID in callback
|
|
||||||
// listener.domainFileRemoved(getDomainFolder(), fileName, null /* fileID */);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Handle addition of new file. If this folder has been visited, listener
|
|
||||||
// * will be notified of new file addition or change
|
|
||||||
// * @param fileName
|
|
||||||
// * @return
|
|
||||||
// */
|
|
||||||
// // TODO: Examine!
|
|
||||||
// GhidraFile fileAddedX(String fileName) {
|
|
||||||
// invalidateFile(fileName);
|
|
||||||
// GhidraFile df = getDomainFile(fileName);
|
|
||||||
// if (visited) {
|
|
||||||
// getFileData(fileName, false);
|
|
||||||
// if (fileList.add(fileName)) {
|
|
||||||
// listener.domainFileAdded(df);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// listenerX.domainFileStatusChanged(df, fileID)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return df;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// // TODO: Examine!
|
|
||||||
// private GhidraFolder addFolderX(String folderName) {
|
|
||||||
// invalidateFolder(folderName, false);
|
|
||||||
// GhidraFolder folder = getDomainFolder(folderName);
|
|
||||||
// if (folderList.add(folderName) && visited) {
|
|
||||||
// listener.domainFolderAdded(folder);
|
|
||||||
// }
|
|
||||||
// return folder;
|
|
||||||
// }
|
|
||||||
|
|
||||||
GhidraFile getDomainFile(String fileName) {
|
GhidraFile getDomainFile(String fileName) {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
try {
|
try {
|
||||||
|
@ -888,6 +928,11 @@ class GhidraFolderData {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the domain folder in this folder with the given subfolderName.
|
||||||
|
* @param subfolderName name of subfolder in this folder to retrieve
|
||||||
|
* @return domain folder or null if there is no file in this folder with the given name.
|
||||||
|
*/
|
||||||
GhidraFolder getDomainFolder(String subfolderName) {
|
GhidraFolder getDomainFolder(String subfolderName) {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
try {
|
try {
|
||||||
|
@ -902,10 +947,26 @@ class GhidraFolderData {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link DomainFolder} instance which corresponds to this folder
|
||||||
|
*/
|
||||||
GhidraFolder getDomainFolder() {
|
GhidraFolder getDomainFolder() {
|
||||||
return new GhidraFolder(parent.getDomainFolder(), name);
|
return new GhidraFolder(parent.getDomainFolder(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a domain object to this folder.
|
||||||
|
* @param fileName domain file name
|
||||||
|
* @param obj domain object to be stored
|
||||||
|
* @param monitor progress monitor
|
||||||
|
* @return domain file created as a result of adding
|
||||||
|
* the domain object to this folder
|
||||||
|
* @throws DuplicateFileException thrown if the file name already exists
|
||||||
|
* @throws InvalidNameException if name is an empty string
|
||||||
|
* or if it contains characters other than alphanumerics.
|
||||||
|
* @throws IOException if IO or access error occurs
|
||||||
|
* @throws CancelledException if the user cancels the create.
|
||||||
|
*/
|
||||||
GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
|
GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
|
||||||
throws InvalidNameException, IOException, CancelledException {
|
throws InvalidNameException, IOException, CancelledException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -937,9 +998,12 @@ class GhidraFolderData {
|
||||||
throw new IOException("File creation failed for unknown reason");
|
throw new IOException("File creation failed for unknown reason");
|
||||||
}
|
}
|
||||||
|
|
||||||
fileManager.setDomainObject(file.getPathname(), doa);
|
projectData.setDomainObject(file.getPathname(), doa);
|
||||||
doa.setDomainFile(file);
|
doa.setDomainFile(file);
|
||||||
doa.setChanged(false);
|
doa.setChanged(false);
|
||||||
|
|
||||||
|
projectData.trackDomainFileInUse(doa);
|
||||||
|
|
||||||
listener.domainFileObjectOpenedForUpdate(file, doa);
|
listener.domainFileObjectOpenedForUpdate(file, doa);
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
|
@ -950,6 +1014,19 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new domain file to this folder.
|
||||||
|
* @param fileName domain file name
|
||||||
|
* @param packFile packed file containing domain file data
|
||||||
|
* @param monitor progress monitor
|
||||||
|
* @return domain file created as a result of adding
|
||||||
|
* the domain object to this folder
|
||||||
|
* @throws DuplicateFileException thrown if the file name already exists
|
||||||
|
* @throws InvalidNameException if name is an empty string
|
||||||
|
* or if it contains characters other than alphanumerics.
|
||||||
|
* @throws IOException if IO or access error occurs
|
||||||
|
* @throws CancelledException if the user cancels the create.
|
||||||
|
*/
|
||||||
GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor)
|
GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor)
|
||||||
throws InvalidNameException, IOException, CancelledException {
|
throws InvalidNameException, IOException, CancelledException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -969,6 +1046,15 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a subfolder within this folder.
|
||||||
|
* @param folderName sub-folder name
|
||||||
|
* @return the new folder
|
||||||
|
* @throws DuplicateFileException if a folder by this name already exists
|
||||||
|
* @throws InvalidNameException if name is an empty string of if it contains characters other
|
||||||
|
* than alphanumerics.
|
||||||
|
* @throws IOException if IO or access error occurs
|
||||||
|
*/
|
||||||
GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException {
|
GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (fileSystem.isReadOnly()) {
|
if (fileSystem.isReadOnly()) {
|
||||||
|
@ -984,6 +1070,11 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes this folder, if empty, from the local filesystem
|
||||||
|
* @throws IOException if IO or access error occurs
|
||||||
|
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
|
||||||
|
*/
|
||||||
void delete() throws IOException {
|
void delete() throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (fileSystem.isReadOnly()) {
|
if (fileSystem.isReadOnly()) {
|
||||||
|
@ -1000,6 +1091,9 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete this folder from the local filesystem if empty
|
||||||
|
*/
|
||||||
void deleteLocalFolderIfEmpty() {
|
void deleteLocalFolderIfEmpty() {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
try {
|
try {
|
||||||
|
@ -1018,6 +1112,19 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move this folder into the newParent folder. If connected to a repository
|
||||||
|
* this moves both private and repository folders/files. If not
|
||||||
|
* connected, only private folders/files are moved.
|
||||||
|
* @param newParent new parent folder within the same project
|
||||||
|
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
|
||||||
|
* it is immutable)
|
||||||
|
* @throws DuplicateFileException if a folder with the same name
|
||||||
|
* already exists in newParent folder.
|
||||||
|
* @throws FileInUseException if this folder or one of its descendants
|
||||||
|
* contains a file which is in-use / checked-out.
|
||||||
|
* @throws IOException thrown if an IO or access error occurs.
|
||||||
|
*/
|
||||||
GhidraFolder moveTo(GhidraFolderData newParent) throws IOException {
|
GhidraFolder moveTo(GhidraFolderData newParent) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) {
|
if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) {
|
||||||
|
@ -1084,11 +1191,17 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the specified folder if an ancestor of this folder
|
||||||
|
* (i.e., parent, grand-parent, etc.).
|
||||||
|
* @param folderData folder to be checked
|
||||||
|
* @return true if the specified folder if an ancestor of this folder
|
||||||
|
*/
|
||||||
boolean isAncestor(GhidraFolderData folderData) {
|
boolean isAncestor(GhidraFolderData folderData) {
|
||||||
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) {
|
if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
|
||||||
// check if projects share a common repository
|
// check if projects share a common repository
|
||||||
RepositoryAdapter myRepository = fileManager.getRepository();
|
RepositoryAdapter myRepository = projectData.getRepository();
|
||||||
RepositoryAdapter otherRepository = folderData.fileManager.getRepository();
|
RepositoryAdapter otherRepository = folderData.projectData.getRepository();
|
||||||
if (myRepository == null || otherRepository == null ||
|
if (myRepository == null || otherRepository == null ||
|
||||||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
|
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
|
||||||
!myRepository.getName().equals(otherRepository.getName())) {
|
!myRepository.getName().equals(otherRepository.getName())) {
|
||||||
|
@ -1105,20 +1218,30 @@ class GhidraFolderData {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
GhidraFolder copyTo(GhidraFolderData newParentData, TaskMonitor monitor)
|
/**
|
||||||
|
* Copy this folder into the newParent folder.
|
||||||
|
* @param newParent new parent folder
|
||||||
|
* @param monitor the task monitor
|
||||||
|
* @return the new copied folder
|
||||||
|
* @throws DuplicateFileException if a folder or file by
|
||||||
|
* this name already exists in the newParent folder
|
||||||
|
* @throws IOException thrown if an IO or access error occurs.
|
||||||
|
* @throws CancelledException if task monitor cancelled operation.
|
||||||
|
*/
|
||||||
|
GhidraFolder copyTo(GhidraFolderData newParent, TaskMonitor monitor)
|
||||||
throws IOException, CancelledException {
|
throws IOException, CancelledException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
if (newParentData.fileSystem.isReadOnly()) {
|
if (newParent.fileSystem.isReadOnly()) {
|
||||||
throw new ReadOnlyException("copyTo permitted to writeable project only");
|
throw new ReadOnlyException("copyTo permitted to writeable project only");
|
||||||
}
|
}
|
||||||
if (isAncestor(newParentData)) {
|
if (isAncestor(newParent)) {
|
||||||
throw new IOException("self-referencing copy not permitted");
|
throw new IOException("self-referencing copy not permitted");
|
||||||
}
|
}
|
||||||
GhidraFolderData newFolderData = newParentData.getFolderData(name, false);
|
GhidraFolderData newFolderData = newParent.getFolderData(name, false);
|
||||||
|
|
||||||
if (newFolderData == null) {
|
if (newFolderData == null) {
|
||||||
try {
|
try {
|
||||||
newFolderData = newParentData.createFolder(name);
|
newFolderData = newParent.createFolder(name);
|
||||||
}
|
}
|
||||||
catch (InvalidNameException e) {
|
catch (InvalidNameException e) {
|
||||||
throw new AssertException("Unexpected error", e);
|
throw new AssertException("Unexpected error", e);
|
||||||
|
@ -1144,22 +1267,46 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException {
|
/**
|
||||||
|
* Create a new link-file in the specified newParent which will reference this folder
|
||||||
|
* (i.e., linked-folder). Restrictions:
|
||||||
|
* <ul>
|
||||||
|
* <li>Specified newParent must reside within a different project since internal linking is
|
||||||
|
* not currently supported.</li>
|
||||||
|
* </ul>
|
||||||
|
* If this folder is associated with a temporary transient project (i.e., not a locally
|
||||||
|
* managed project) the generated link will refer to the remote folder with a remote
|
||||||
|
* Ghidra URL, otherwise a local project storage path will be used.
|
||||||
|
* @param newParent new parent folder where link-file is to be created
|
||||||
|
* @return newly created domain file (i.e., link-file) or null if link use not supported.
|
||||||
|
* @throws IOException if an IO or access error occurs.
|
||||||
|
*/
|
||||||
|
DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
String linkFilename = name;
|
String linkFilename = name;
|
||||||
if (linkFilename == null) {
|
if (linkFilename == null) {
|
||||||
if (fileManager instanceof TransientProjectData) {
|
if (projectData instanceof TransientProjectData) {
|
||||||
linkFilename = fileManager.getRepository().getName();
|
linkFilename = projectData.getRepository().getName();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
linkFilename = fileManager.getProjectLocator().getName();
|
linkFilename = projectData.getProjectLocator().getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename,
|
return newParent.copyAsLink(projectData, getPathname(), linkFilename,
|
||||||
FolderLinkContentHandler.INSTANCE);
|
FolderLinkContentHandler.INSTANCE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a link-file within this folder. The link-file may correspond to various types of
|
||||||
|
* content (e.g., Program, Trace, Folder, etc.) based upon specified link handler.
|
||||||
|
* @param sourceProjectData referenced content project data within which specified path exists.
|
||||||
|
* @param pathname path of referenced content with source project data
|
||||||
|
* @param linkFilename name of link-file to be created within this folder.
|
||||||
|
* @param lh link file handler used to create specific link file.
|
||||||
|
* @return link-file
|
||||||
|
* @throws IOException if IO error occurs during link creation
|
||||||
|
*/
|
||||||
DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename,
|
DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename,
|
||||||
LinkHandler<?> lh) throws IOException {
|
LinkHandler<?> lh) throws IOException {
|
||||||
synchronized (fileSystem) {
|
synchronized (fileSystem) {
|
||||||
|
@ -1167,7 +1314,7 @@ class GhidraFolderData {
|
||||||
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
|
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceProjectData == fileManager) {
|
if (sourceProjectData == projectData) {
|
||||||
// internal linking not yet supported
|
// internal linking not yet supported
|
||||||
Msg.error(this, "Internal file/folder links not yet supported");
|
Msg.error(this, "Internal file/folder links not yet supported");
|
||||||
return null;
|
return null;
|
||||||
|
@ -1177,13 +1324,12 @@ class GhidraFolderData {
|
||||||
if (sourceProjectData instanceof TransientProjectData) {
|
if (sourceProjectData instanceof TransientProjectData) {
|
||||||
RepositoryAdapter repository = sourceProjectData.getRepository();
|
RepositoryAdapter repository = sourceProjectData.getRepository();
|
||||||
ServerInfo serverInfo = repository.getServerInfo();
|
ServerInfo serverInfo = repository.getServerInfo();
|
||||||
ghidraUrl =
|
ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
|
||||||
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
serverInfo.getPortNumber(), repository.getName(), pathname);
|
||||||
repository.getName(), pathname);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
|
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
|
||||||
if (projectLocator.equals(fileManager.getProjectLocator())) {
|
if (projectLocator.equals(projectData.getProjectLocator())) {
|
||||||
return null; // local internal linking not supported
|
return null; // local internal linking not supported
|
||||||
}
|
}
|
||||||
ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null);
|
ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null);
|
||||||
|
@ -1216,6 +1362,14 @@ class GhidraFolderData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a non-conflicting file name for this folder based upon the specified preferred name.
|
||||||
|
* NOTE: This method is subject to race conditions where returned name could conflict by the
|
||||||
|
* time it is actually used.
|
||||||
|
* @param preferredName preferred file name
|
||||||
|
* @return non-conflicting file name
|
||||||
|
* @throws IOException if an IO error occurs during file checks
|
||||||
|
*/
|
||||||
String getTargetName(String preferredName) throws IOException {
|
String getTargetName(String preferredName) throws IOException {
|
||||||
String newName = preferredName;
|
String newName = preferredName;
|
||||||
int i = 1;
|
int i = 1;
|
||||||
|
@ -1242,11 +1396,11 @@ class GhidraFolderData {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||||
if (projectLocator.isTransient()) {
|
if (projectLocator.isTransient()) {
|
||||||
return fileManager.getProjectLocator().getName() + getPathname();
|
return projectData.getProjectLocator().getName() + getPathname();
|
||||||
}
|
}
|
||||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
return projectData.getProjectLocator().getName() + ":" + getPathname();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,8 +19,8 @@ import ghidra.framework.model.DomainFolderChangeListener;
|
||||||
|
|
||||||
public class RootGhidraFolder extends GhidraFolder {
|
public class RootGhidraFolder extends GhidraFolder {
|
||||||
|
|
||||||
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||||
super(fileManager, listener);
|
super(projectData, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem;
|
||||||
|
|
||||||
public class RootGhidraFolderData extends GhidraFolderData {
|
public class RootGhidraFolderData extends GhidraFolderData {
|
||||||
|
|
||||||
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||||
super(fileManager, listener);
|
super(projectData, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
GhidraFolder getDomainFolder() {
|
GhidraFolder getDomainFolder() {
|
||||||
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
|
return new RootGhidraFolder(getProjectData(), getChangeListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provided for testing use only
|
* Provided for testing use only
|
||||||
* @param fs
|
* @param fs versioned file system
|
||||||
*/
|
*/
|
||||||
void setVersionedFileSystem(FileSystem fs) {
|
void setVersionedFileSystem(FileSystem fs) {
|
||||||
versionedFileSystem = fs;
|
versionedFileSystem = fs;
|
||||||
|
|
|
@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
public final static String READ_ONLY_PROPERTY = "READ_ONLY";
|
public final static String READ_ONLY_PROPERTY = "READ_ONLY";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the StoredObj that is associated with the data.
|
* Get the name of this project file
|
||||||
* @return the name
|
* @return the name
|
||||||
*/
|
*/
|
||||||
public String getName();
|
public String getName();
|
||||||
|
@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
public DomainFile setName(String newName) throws InvalidNameException, IOException;
|
public DomainFile setName(String newName) throws InvalidNameException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path name to the domain object.
|
* Returns the full path name to this file
|
||||||
* @return the path name
|
* @return the path name
|
||||||
*/
|
*/
|
||||||
public String getPathname();
|
public String getPathname();
|
||||||
|
@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
public ProjectLocator getProjectLocator();
|
public ProjectLocator getProjectLocator();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns content-type string
|
* Returns content-type string for this file
|
||||||
* @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT}
|
* @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT}
|
||||||
* or {@link ContentHandler#UNKNOWN_CONTENT}.
|
* or {@link ContentHandler#UNKNOWN_CONTENT}.
|
||||||
*/
|
*/
|
||||||
|
@ -134,8 +134,11 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns changes made to versioned file by others since checkout was performed.
|
* Returns changes made to versioned file by others since checkout was performed.
|
||||||
|
* NOTE: This method is unable to cope with version issues which may require an
|
||||||
|
* upgrade.
|
||||||
* @return change set or null
|
* @return change set or null
|
||||||
* @throws VersionException latest version was created with a newer version of software
|
* @throws VersionException latest version was created with a different version of software
|
||||||
|
* which prevents rapid determination of change set.
|
||||||
* @throws IOException if a folder item access error occurs or change set was
|
* @throws IOException if a folder item access error occurs or change set was
|
||||||
* produced by newer version of software and can not be read
|
* produced by newer version of software and can not be read
|
||||||
*/
|
*/
|
||||||
|
@ -426,7 +429,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
* @param keep if true, the private database will be renamed with a .keep
|
* @param keep if true, the private database will be renamed with a .keep
|
||||||
* extension.
|
* extension.
|
||||||
* @throws NotConnectedException if shared project and not connected to repository
|
* @throws NotConnectedException if shared project and not connected to repository
|
||||||
* @throws FileInUseException if this file is in-use / checked-out.
|
* @throws FileInUseException if this file is in-use.
|
||||||
* @throws IOException if file is not checked-out or an IO / access error occurs.
|
* @throws IOException if file is not checked-out or an IO / access error occurs.
|
||||||
*/
|
*/
|
||||||
public void undoCheckout(boolean keep) throws IOException;
|
public void undoCheckout(boolean keep) throws IOException;
|
||||||
|
@ -549,7 +552,8 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of consumers (Objects) for this domain file.
|
* Get the list of consumers (Objects) for this domain file.
|
||||||
* @return empty array list if there are no consumers
|
* @return true if linking is supported allowing a link-file to be created which
|
||||||
|
* references this file, else false.
|
||||||
*/
|
*/
|
||||||
public List<?> getConsumers();
|
public List<?> getConsumers();
|
||||||
|
|
||||||
|
@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||||
/**
|
/**
|
||||||
* Returns the length of this domain file. This size is the minimum disk space
|
* Returns the length of this domain file. This size is the minimum disk space
|
||||||
* used for storing this file, but does not account for additional storage space
|
* used for storing this file, but does not account for additional storage space
|
||||||
* used to tracks changes, etc.
|
* used to track changes, etc.
|
||||||
* @return file length
|
* @return file length
|
||||||
* @throws IOException if IO or access error occurs
|
* @throws IOException if IO or access error occurs
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||||
public ProjectData getProjectData();
|
public ProjectData getProjectData();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path name to the domain object.
|
* Returns the full path name to this folder
|
||||||
* @return the path name
|
* @return the path name
|
||||||
*/
|
*/
|
||||||
public String getPathname();
|
public String getPathname();
|
||||||
|
@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||||
throws InvalidNameException, IOException, CancelledException;
|
throws InvalidNameException, IOException, CancelledException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a subfolder of this folder.
|
* Create a subfolder within this folder.
|
||||||
* @param folderName sub-folder name
|
* @param folderName sub-folder name
|
||||||
* @return the folder
|
* @return the new folder
|
||||||
* @throws DuplicateFileException if a folder by
|
* @throws DuplicateFileException if a folder by this name already exists
|
||||||
* this name already exists
|
|
||||||
* @throws InvalidNameException if name is an empty string of if it contains characters other
|
* @throws InvalidNameException if name is an empty string of if it contains characters other
|
||||||
* than alphanumerics.
|
* than alphanumerics.
|
||||||
* @throws IOException if IO or access error occurs
|
* @throws IOException if IO or access error occurs
|
||||||
|
@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||||
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
|
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes this folder and all of its contents
|
* Deletes this folder, if empty, from the local filesystem
|
||||||
* @throws IOException if IO or access error occurs
|
* @throws IOException if IO or access error occurs
|
||||||
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
|
* @throws FolderNotEmptyException Thrown if this folder is not empty.
|
||||||
*/
|
*/
|
||||||
public void delete() throws IOException;
|
public void delete() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move this folder into the newParent folder. If connected to an archive
|
* Move this folder into the newParent folder. If connected to a repository
|
||||||
* this affects both private and repository folders and files. If not
|
* this moves both private and repository folders/files. If not
|
||||||
* connected, only private folders and files are affected.
|
* connected, only private folders/files are moved.
|
||||||
* @param newParent new parent folder within the same project
|
* @param newParent new parent folder within the same project
|
||||||
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
|
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
|
||||||
* it is immutable)
|
* it is immutable)
|
||||||
|
@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||||
* Copy this folder into the newParent folder.
|
* Copy this folder into the newParent folder.
|
||||||
* @param newParent new parent folder
|
* @param newParent new parent folder
|
||||||
* @param monitor the task monitor
|
* @param monitor the task monitor
|
||||||
* @return the copied folder
|
* @return the new copied folder
|
||||||
* @throws DuplicateFileException if a folder or file by
|
* @throws DuplicateFileException if a folder or file by
|
||||||
* this name already exists in the newParent folder
|
* this name already exists in the newParent folder
|
||||||
* @throws IOException thrown if an IO or access error occurs.
|
* @throws IOException thrown if an IO or access error occurs.
|
||||||
|
@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||||
throws IOException, CancelledException;
|
throws IOException, CancelledException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy this folder into the newParent folder as a link file. Restrictions:
|
* Create a new link-file in the specified newParent which will reference this folder
|
||||||
|
* (i.e., linked-folder). Restrictions:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Specified newParent must reside within a different project since internal linking is
|
* <li>Specified newParent must reside within a different project since internal linking is
|
||||||
* not currently supported.</li>
|
* not currently supported.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* If this folder is associated with a temporary transient project (i.e., not a locally
|
* If this folder is associated with a temporary transient project (i.e., not a locally
|
||||||
* managed project) the generated link will refer to the remote file with a remote
|
* managed project) the generated link will refer to the remote folder with a remote
|
||||||
* Ghidra URL, otherwise a local project storage path will be used.
|
* Ghidra URL, otherwise a local project storage path will be used.
|
||||||
* @param newParent new parent folder
|
* @param newParent new parent folder where link-file is to be created
|
||||||
* @return newly created domain file or null if link use not supported.
|
* @return newly created domain file (i.e., link-file) or null if link use not supported.
|
||||||
* @throws IOException if an IO or access error occurs.
|
* @throws IOException if an IO or access error occurs.
|
||||||
*/
|
*/
|
||||||
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;
|
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,5 +19,10 @@ package ghidra.framework.model;
|
||||||
* An interface that allows for a callback when a {@link DomainObject} is closed.
|
* An interface that allows for a callback when a {@link DomainObject} is closed.
|
||||||
*/
|
*/
|
||||||
public interface DomainObjectClosedListener {
|
public interface DomainObjectClosedListener {
|
||||||
public void domainObjectClosed();
|
|
||||||
|
/**
|
||||||
|
* Callback indicating that the specified {@link DomainObject} has been closed.
|
||||||
|
* @param dobj domain object
|
||||||
|
*/
|
||||||
|
public void domainObjectClosed(DomainObject dobj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,8 +191,10 @@ public interface ProjectData {
|
||||||
TaskMonitor monitor) throws IOException, CancelledException;
|
TaskMonitor monitor) throws IOException, CancelledException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the project storage associated with this project data object.
|
* Initiate disposal of this project data object. Any files already open will delay
|
||||||
* NOTE: This should not be invoked if this object is utilized by a Project instance.
|
* disposal until they are closed.
|
||||||
|
* NOTE: This should only be invoked by the controlling object which created/opened this
|
||||||
|
* instance to avoid premature disposal.
|
||||||
*/
|
*/
|
||||||
public void close();
|
public void close();
|
||||||
|
|
||||||
|
@ -209,4 +211,18 @@ public interface ProjectData {
|
||||||
*/
|
*/
|
||||||
public void testValidName(String name, boolean isPath) throws InvalidNameException;
|
public void testValidName(String name, boolean isPath) throws InvalidNameException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a repository URL which corresponds to this project data if applicable.
|
||||||
|
* Local private projects will return null;
|
||||||
|
* @return repository URL which corresponds to this project data or null if not applicable.
|
||||||
|
*/
|
||||||
|
public URL getSharedProjectURL();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a local URL which corresponds to this project data if applicable.
|
||||||
|
* Remote transient project data will return null;
|
||||||
|
* @return local URL which corresponds to this project data or null if not applicable.
|
||||||
|
*/
|
||||||
|
public URL getLocalProjectURL();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.jdom.input.SAXBuilder;
|
||||||
import org.jdom.output.XMLOutputter;
|
import org.jdom.output.XMLOutputter;
|
||||||
|
|
||||||
import ghidra.framework.client.RepositoryAdapter;
|
import ghidra.framework.client.RepositoryAdapter;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.data.TransientDataManager;
|
import ghidra.framework.data.TransientDataManager;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.options.SaveState;
|
import ghidra.framework.options.SaveState;
|
||||||
|
@ -58,7 +58,7 @@ public class DefaultProject implements Project {
|
||||||
private DefaultProjectManager projectManager;
|
private DefaultProjectManager projectManager;
|
||||||
|
|
||||||
private ProjectLocator projectLocator;
|
private ProjectLocator projectLocator;
|
||||||
private ProjectFileManager fileMgr;
|
private DefaultProjectData projectData;
|
||||||
private ToolManagerImpl toolManager;
|
private ToolManagerImpl toolManager;
|
||||||
|
|
||||||
private boolean changed; // flag for whether the project configuration has changed
|
private boolean changed; // flag for whether the project configuration has changed
|
||||||
|
@ -66,7 +66,7 @@ public class DefaultProject implements Project {
|
||||||
|
|
||||||
private Map<String, SaveState> dataMap = new HashMap<>();
|
private Map<String, SaveState> dataMap = new HashMap<>();
|
||||||
private Map<String, ToolTemplate> projectConfigMap = new HashMap<>();
|
private Map<String, ToolTemplate> projectConfigMap = new HashMap<>();
|
||||||
private Map<URL, ProjectFileManager> otherViews = new HashMap<>();
|
private Map<URL, DefaultProjectData> otherViewsMap = new HashMap<>();
|
||||||
private Set<URL> visibleViews = new HashSet<>();
|
private Set<URL> visibleViews = new HashSet<>();
|
||||||
private WeakSet<ProjectViewListener> viewListeners =
|
private WeakSet<ProjectViewListener> viewListeners =
|
||||||
WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||||
|
@ -86,21 +86,10 @@ public class DefaultProject implements Project {
|
||||||
this.projectManager = projectManager;
|
this.projectManager = projectManager;
|
||||||
this.projectLocator = projectLocator;
|
this.projectLocator = projectLocator;
|
||||||
|
|
||||||
boolean success = false;
|
Msg.info(this, "Creating project: " + projectLocator.toString());
|
||||||
try {
|
projectData = new DefaultProjectData(projectLocator, repository, true);
|
||||||
Msg.info(this, "Creating project: " + projectLocator.toString());
|
if (!SystemUtilities.isInHeadlessMode()) {
|
||||||
fileMgr = new ProjectFileManager(projectLocator, repository, true);
|
toolManager = new ToolManagerImpl(this);
|
||||||
if (!SystemUtilities.isInHeadlessMode()) {
|
|
||||||
toolManager = new ToolManagerImpl(this);
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (!success) {
|
|
||||||
if (fileMgr != null) {
|
|
||||||
fileMgr.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
initializeNewProject();
|
initializeNewProject();
|
||||||
}
|
}
|
||||||
|
@ -122,28 +111,18 @@ public class DefaultProject implements Project {
|
||||||
this.projectManager = projectManager;
|
this.projectManager = projectManager;
|
||||||
this.projectLocator = projectLocator;
|
this.projectLocator = projectLocator;
|
||||||
|
|
||||||
boolean success = false;
|
Msg.info(this, "Opening project: " + projectLocator.toString());
|
||||||
try {
|
projectData = new DefaultProjectData(projectLocator, true, resetOwner);
|
||||||
Msg.info(this, "Opening project: " + projectLocator.toString());
|
if (!SystemUtilities.isInHeadlessMode()) {
|
||||||
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner);
|
toolManager = new ToolManagerImpl(this);
|
||||||
if (!SystemUtilities.isInHeadlessMode()) {
|
|
||||||
toolManager = new ToolManagerImpl(this);
|
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (!success) {
|
|
||||||
if (fileMgr != null) {
|
|
||||||
fileMgr.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for opening a URL-based project
|
* Constructor for opening a URL-based project
|
||||||
*
|
*
|
||||||
* @param connection project connection
|
* @param projectManager the manager of this project
|
||||||
|
* @param connection project URL connection (not previously used)
|
||||||
* @throws IOException if I/O error occurs.
|
* @throws IOException if I/O error occurs.
|
||||||
*/
|
*/
|
||||||
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
|
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
|
||||||
|
@ -151,27 +130,17 @@ public class DefaultProject implements Project {
|
||||||
|
|
||||||
this.projectManager = projectManager;
|
this.projectManager = projectManager;
|
||||||
|
|
||||||
boolean success = false;
|
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
||||||
try {
|
projectData = (DefaultProjectData) connection.getProjectData();
|
||||||
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
if (projectData == null) {
|
||||||
fileMgr = (ProjectFileManager) connection.getProjectData();
|
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
||||||
if (fileMgr == null) {
|
}
|
||||||
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
|
||||||
}
|
|
||||||
|
|
||||||
projectLocator = fileMgr.getProjectLocator();
|
projectLocator = projectData.getProjectLocator();
|
||||||
if (!SystemUtilities.isInHeadlessMode()) {
|
if (!SystemUtilities.isInHeadlessMode()) {
|
||||||
toolManager = new ToolManagerImpl(this);
|
toolManager = new ToolManagerImpl(this);
|
||||||
}
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (!success) {
|
|
||||||
if (fileMgr != null) {
|
|
||||||
fileMgr.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeNewProject();
|
initializeNewProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,20 +269,20 @@ public class DefaultProject implements Project {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData();
|
DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
|
||||||
if (projectData == null) {
|
if (projectData == null) {
|
||||||
throw new IOException(
|
throw new IOException(
|
||||||
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
|
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
|
||||||
}
|
}
|
||||||
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
|
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
|
||||||
|
|
||||||
otherViews.put(url, projectData);
|
otherViewsMap.put(url, projectData);
|
||||||
return projectData;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
|
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
if (isClosed) {
|
if (isClosed) {
|
||||||
throw new IOException("project is closed");
|
throw new IOException("project is closed");
|
||||||
}
|
}
|
||||||
|
@ -322,7 +291,7 @@ public class DefaultProject implements Project {
|
||||||
throw new IOException("Invalid Ghidra URL specified: " + url);
|
throw new IOException("Invalid Ghidra URL specified: " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectData projectData = otherViews.get(url);
|
ProjectData projectData = otherViewsMap.get(url);
|
||||||
if (projectData == null) {
|
if (projectData == null) {
|
||||||
projectData = openProjectView(url);
|
projectData = openProjectView(url);
|
||||||
}
|
}
|
||||||
|
@ -340,13 +309,13 @@ public class DefaultProject implements Project {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void removeProjectView(URL url) {
|
public void removeProjectView(URL url) {
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
ProjectFileManager dataMgr = otherViews.remove(url);
|
DefaultProjectData dataMgr = otherViewsMap.remove(url);
|
||||||
if (dataMgr != null) {
|
if (dataMgr != null) {
|
||||||
if (visibleViews.remove(url)) {
|
if (visibleViews.remove(url)) {
|
||||||
notifyVisibleViewRemoved(url);
|
notifyVisibleViewRemoved(url);
|
||||||
}
|
}
|
||||||
dataMgr.dispose();
|
dataMgr.close();
|
||||||
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
|
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
@ -401,22 +370,20 @@ public class DefaultProject implements Project {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepositoryAdapter getRepository() {
|
public RepositoryAdapter getRepository() {
|
||||||
return fileMgr.getRepository();
|
return projectData.getRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
|
|
||||||
Iterator<ProjectFileManager> iter = otherViews.values().iterator();
|
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
ProjectFileManager dataMgr = iter.next();
|
|
||||||
if (dataMgr != null) {
|
if (dataMgr != null) {
|
||||||
dataMgr.dispose();
|
dataMgr.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
otherViews.clear();
|
otherViewsMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -429,7 +396,7 @@ public class DefaultProject implements Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
fileMgr.dispose();
|
projectData.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,7 +416,7 @@ public class DefaultProject implements Project {
|
||||||
@Override
|
@Override
|
||||||
public void restore() {
|
public void restore() {
|
||||||
// if there is a saved project, restore it
|
// if there is a saved project, restore it
|
||||||
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
|
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
|
||||||
String errorMsg = null;
|
String errorMsg = null;
|
||||||
Throwable error = null;
|
Throwable error = null;
|
||||||
try {
|
try {
|
||||||
|
@ -593,7 +560,7 @@ public class DefaultProject implements Project {
|
||||||
try {
|
try {
|
||||||
// save tool state
|
// save tool state
|
||||||
root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state
|
root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state
|
||||||
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
|
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
|
||||||
OutputStream os = new FileOutputStream(saveFile);
|
OutputStream os = new FileOutputStream(saveFile);
|
||||||
Document doc = new Document(root);
|
Document doc = new Document(root);
|
||||||
XMLOutputter xmlOut = new GenericXMLOutputter();
|
XMLOutputter xmlOut = new GenericXMLOutputter();
|
||||||
|
@ -629,15 +596,14 @@ public class DefaultProject implements Project {
|
||||||
@Override
|
@Override
|
||||||
public List<DomainFile> getOpenData() {
|
public List<DomainFile> getOpenData() {
|
||||||
ArrayList<DomainFile> openFiles = new ArrayList<>();
|
ArrayList<DomainFile> openFiles = new ArrayList<>();
|
||||||
fileMgr.findOpenFiles(openFiles);
|
projectData.findOpenFiles(openFiles);
|
||||||
ProjectData[] viewedProjs = getViewedProjectData();
|
ProjectData[] viewedProjs = getViewedProjectData();
|
||||||
for (ProjectData viewedProj : viewedProjs) {
|
for (ProjectData viewedProj : viewedProjs) {
|
||||||
((ProjectFileManager) viewedProj).findOpenFiles(openFiles);
|
((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
|
||||||
}
|
}
|
||||||
List<DomainFile> list = new ArrayList<>();
|
List<DomainFile> list = new ArrayList<>();
|
||||||
TransientDataManager.getTransients(list);
|
TransientDataManager.getTransients(list);
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (DomainFile df : list) {
|
||||||
DomainFile df = list.get(i);
|
|
||||||
if (df != null && df.isOpen()) {
|
if (df != null && df.isOpen()) {
|
||||||
openFiles.add(df);
|
openFiles.add(df);
|
||||||
}
|
}
|
||||||
|
@ -646,8 +612,8 @@ public class DefaultProject implements Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectFileManager getProjectData() {
|
public DefaultProjectData getProjectData() {
|
||||||
return fileMgr;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -662,12 +628,12 @@ public class DefaultProject implements Project {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectData getProjectData(ProjectLocator locator) {
|
public ProjectData getProjectData(ProjectLocator locator) {
|
||||||
if (locator.equals(fileMgr.getProjectLocator())) {
|
if (locator.equals(projectData.getProjectLocator())) {
|
||||||
return fileMgr;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
for (ProjectData data : otherViews.values()) {
|
for (ProjectData data : otherViewsMap.values()) {
|
||||||
if (locator.equals(data.getProjectLocator())) {
|
if (locator.equals(data.getProjectLocator())) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -680,30 +646,30 @@ public class DefaultProject implements Project {
|
||||||
@Override
|
@Override
|
||||||
public ProjectData getProjectData(URL url) {
|
public ProjectData getProjectData(URL url) {
|
||||||
if (projectLocator.getURL().equals(url)) {
|
if (projectLocator.getURL().equals(url)) {
|
||||||
return fileMgr;
|
return projectData;
|
||||||
}
|
}
|
||||||
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
|
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
|
||||||
if (remoteURL != null) {
|
if (remoteURL != null) {
|
||||||
remoteURL = GhidraURL.getProjectURL(url);
|
remoteURL = GhidraURL.getProjectURL(url);
|
||||||
}
|
}
|
||||||
if (remoteURL.equals(url)) {
|
if (remoteURL.equals(url)) {
|
||||||
return fileMgr;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
return otherViews.get(url);
|
return otherViewsMap.get(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectData[] getViewedProjectData() {
|
public ProjectData[] getViewedProjectData() {
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
|
|
||||||
// only return visible viewed project
|
// only return visible viewed project
|
||||||
List<ProjectData> list = new ArrayList<>();
|
List<ProjectData> list = new ArrayList<>();
|
||||||
for (URL url : otherViews.keySet()) {
|
for (URL url : otherViewsMap.keySet()) {
|
||||||
if (visibleViews.contains(url)) {
|
if (visibleViews.contains(url)) {
|
||||||
list.add(otherViews.get(url));
|
list.add(otherViewsMap.get(url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,11 +681,9 @@ public class DefaultProject implements Project {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseFiles(Object consumer) {
|
public void releaseFiles(Object consumer) {
|
||||||
fileMgr.releaseDomainFiles(consumer);
|
projectData.releaseDomainFiles(consumer);
|
||||||
synchronized (otherViews) {
|
synchronized (otherViewsMap) {
|
||||||
Iterator<ProjectFileManager> it = otherViews.values().iterator();
|
for (DefaultProjectData mgr : otherViewsMap.values()) {
|
||||||
while (it.hasNext()) {
|
|
||||||
ProjectFileManager mgr = it.next();
|
|
||||||
mgr.releaseDomainFiles(consumer);
|
mgr.releaseDomainFiles(consumer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
|
||||||
}
|
}
|
||||||
catch (RepositoryNotFoundException e) {
|
catch (RepositoryNotFoundException e) {
|
||||||
statusCode = StatusCode.NOT_FOUND;
|
statusCode = StatusCode.NOT_FOUND;
|
||||||
|
return statusCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!repositoryServerAdapter.isCancelled()) {
|
else if (!repositoryServerAdapter.isCancelled()) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import java.net.URL;
|
||||||
|
|
||||||
import ghidra.framework.client.NotConnectedException;
|
import ghidra.framework.client.NotConnectedException;
|
||||||
import ghidra.framework.client.RepositoryAdapter;
|
import ghidra.framework.client.RepositoryAdapter;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.ProjectLocator;
|
import ghidra.framework.model.ProjectLocator;
|
||||||
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
|
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
|
@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
|
||||||
* @return project data instance or null if project not found
|
* @return project data instance or null if project not found
|
||||||
* @throws IOException if IO error occurs
|
* @throws IOException if IO error occurs
|
||||||
*/
|
*/
|
||||||
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
||||||
if (connect(readOnlyAccess) != StatusCode.OK) {
|
if (connect(readOnlyAccess) != StatusCode.OK) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false);
|
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
|
||||||
}
|
}
|
||||||
catch (NotOwnerException | ReadOnlyException e) {
|
catch (NotOwnerException | ReadOnlyException e) {
|
||||||
statusCode = StatusCode.UNAUTHORIZED;
|
statusCode = StatusCode.UNAUTHORIZED;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
|
||||||
import ghidra.framework.client.*;
|
import ghidra.framework.client.*;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.ProjectData;
|
import ghidra.framework.model.ProjectData;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
|
@ -69,38 +69,6 @@ public class GhidraURLConnection extends URLConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consider implementing request and response headers
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Ghidra Status-Code 200: OK.
|
|
||||||
// */
|
|
||||||
// public static final int GHIDRA_OK = 200;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ghidra Status-Code 401: Unauthorized.
|
|
||||||
// * This response code includes a variety of connection errors
|
|
||||||
// * which are reported/logged by the Ghidra Server support code.
|
|
||||||
// */
|
|
||||||
// public static final int GHIDRA_UNAUTHORIZED = 401;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ghidra Status-Code 404: Not Found.
|
|
||||||
// */
|
|
||||||
// public static final int GHIDRA_NOT_FOUND = 404;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ghidra Status-Code 423: Locked
|
|
||||||
// * Caused by attempt to open local project data with write-access when project is
|
|
||||||
// * already opened and locked.
|
|
||||||
// */
|
|
||||||
// public static final int GHIDRA_LOCKED = 423;
|
|
||||||
//
|
|
||||||
// /**
|
|
||||||
// * Ghidra Status-Code 503: Unavailable
|
|
||||||
// * Caused by other connection failure
|
|
||||||
// */
|
|
||||||
// public static final int GHIDRA_UNAVAILABLE = 503;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object.
|
* Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object.
|
||||||
* @see GhidraURLWrappedContent
|
* @see GhidraURLWrappedContent
|
||||||
|
@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
|
||||||
|
|
||||||
private GhidraProtocolConnector protocolConnector;
|
private GhidraProtocolConnector protocolConnector;
|
||||||
|
|
||||||
private ProjectFileManager projectData;
|
private DefaultProjectData projectData;
|
||||||
private Object refObject;
|
private Object refObject;
|
||||||
|
|
||||||
private boolean readOnly = true;
|
private boolean readOnly = true;
|
||||||
|
@ -270,12 +238,17 @@ public class GhidraURLConnection extends URLConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If URL connects and corresponds to a valid repository, this method
|
* If URL connects and corresponds to a valid repository or local project, this method
|
||||||
* may be used to obtain the associated ProjectData object. The caller is
|
* may be used to obtain the associated ProjectData object. The caller is
|
||||||
* responsible for closing the returned project data when no longer in-use,
|
* responsible for properly {@link ProjectData#close() closing} the returned project data
|
||||||
* failure to do so may prevent release of repository handle to server.
|
* instance when no longer in-use, failure to do so may prevent release of repository handle
|
||||||
* Only a single call to this method is permitted.
|
* to server until process exits. It is important that {@link ProjectData#close()} is
|
||||||
* @return transient project data or null if unavailable
|
* invoked once, and only once, per call to this method to ensure project "use" tracking
|
||||||
|
* is properly maintained. Improperly invoking the close method on a shared transient
|
||||||
|
* {@link ProjectData} instance may cause the underlying storage to be prematurely
|
||||||
|
* disposed.
|
||||||
|
*
|
||||||
|
* @return project data which corresponds to this connection or null if unavailable
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
public ProjectData getProjectData() throws IOException {
|
public ProjectData getProjectData() throws IOException {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.util.InvalidNameException;
|
import ghidra.util.InvalidNameException;
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
|
||||||
|
|
||||||
private List<Object> consumers = new ArrayList<Object>();
|
private List<Object> consumers = new ArrayList<Object>();
|
||||||
|
|
||||||
private ProjectData projectData;
|
private DefaultProjectData projectData;
|
||||||
private Object refObject;
|
private Object refObject;
|
||||||
|
|
||||||
public GhidraURLWrappedContent(GhidraURLConnection c) {
|
public GhidraURLWrappedContent(GhidraURLConnection c) {
|
||||||
|
@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close associated {@link ProjectData} when all consumers have released wrapped object.
|
* Close associated {@link ProjectData} when all consumers have released wrapped object.
|
||||||
|
* Underlying project data instance may remain active until all open project files have been
|
||||||
|
* released/closed.
|
||||||
*/
|
*/
|
||||||
private void closeProjectData() {
|
private void closeProjectData() {
|
||||||
if (projectData != null) {
|
if (projectData != null) {
|
||||||
|
@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
|
||||||
refObject = null;
|
refObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException,
|
private DomainFolder getExplicitFolder(String folderPath)
|
||||||
IOException {
|
throws InvalidNameException, IOException {
|
||||||
DomainFolder folder = projectData.getRootFolder();
|
DomainFolder folder = projectData.getRootFolder();
|
||||||
for (String name : folderPath.substring(1).split("/")) {
|
for (String name : folderPath.substring(1).split("/")) {
|
||||||
DomainFolder subfolder = folder.getFolder(name);
|
DomainFolder subfolder = folder.getFolder(name);
|
||||||
|
@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
projectData = c.getProjectData();
|
projectData = (DefaultProjectData) c.getProjectData();
|
||||||
|
|
||||||
String folderItemName = c.getFolderItemName();
|
String folderItemName = c.getFolderItemName();
|
||||||
String folderPath = c.getFolderPath();
|
String folderPath = c.getFolderPath();
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
package ghidra.framework.protocol.ghidra;
|
package ghidra.framework.protocol.ghidra;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
import generic.timer.GhidraSwinglessTimer;
|
import generic.timer.GhidraSwinglessTimer;
|
||||||
import ghidra.framework.client.RepositoryAdapter;
|
import ghidra.framework.client.RepositoryAdapter;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.ProjectLocator;
|
import ghidra.framework.model.ProjectLocator;
|
||||||
import ghidra.framework.remote.RepositoryHandle;
|
import ghidra.framework.remote.RepositoryHandle;
|
||||||
import ghidra.framework.store.LockException;
|
import ghidra.framework.store.LockException;
|
||||||
|
@ -27,7 +28,7 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.SystemUtilities;
|
import ghidra.util.SystemUtilities;
|
||||||
import utilities.util.FileUtilities;
|
import utilities.util.FileUtilities;
|
||||||
|
|
||||||
public class TransientProjectData extends ProjectFileManager {
|
public class TransientProjectData extends DefaultProjectData {
|
||||||
|
|
||||||
private TransientProjectManager dataMgr;
|
private TransientProjectManager dataMgr;
|
||||||
final RepositoryInfo repositoryInfo;
|
final RepositoryInfo repositoryInfo;
|
||||||
|
@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
|
||||||
}
|
}
|
||||||
stopCleanupTimer();
|
stopCleanupTimer();
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
|
||||||
|
|
||||||
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " +
|
String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
|
||||||
getProjectLocator().getProjectDir());
|
getProjectLocator().getProjectDir() + ", URL: " + repositoryInfo.getURL();
|
||||||
|
if (instanceUseCount != 0) {
|
||||||
|
Msg.error(this, "Premature removal of active" + msgTail);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Msg.debug(this, "Removing idle" + msgTail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dataMgr.cleanupProjectData(repositoryInfo, this);
|
dataMgr.cleanupProjectData(repositoryInfo, this);
|
||||||
|
|
||||||
super.dispose(); // disconnects repository
|
super.dispose(); // disconnects repository
|
||||||
|
|
||||||
// TODO: There could still be open files if they have not been properly released/closed !!
|
// Remove temporary project storage
|
||||||
|
// NOTE: This could be affected by project files which still remain open
|
||||||
ProjectLocator locator = getProjectLocator();
|
ProjectLocator locator = getProjectLocator();
|
||||||
FileUtilities.deleteDir(locator.getProjectDir());
|
FileUtilities.deleteDir(locator.getProjectDir());
|
||||||
locator.getMarkerFile().delete();
|
locator.getMarkerFile().delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void close() {
|
||||||
// prevent normal disposal - rely on finalizer and shutdown hook
|
// prevent normal disposal - rely on finalizer and shutdown hook
|
||||||
synchronized (cleanupTimer) {
|
synchronized (cleanupTimer) {
|
||||||
if (instanceUseCount == 0) {
|
if (instanceUseCount == 0) {
|
||||||
|
@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void dispose() {
|
||||||
try {
|
// rely on forcedDispose to invoke super.dispose()
|
||||||
forcedDispose();
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
// ignore errors during finalize
|
|
||||||
}
|
|
||||||
super.finalize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getSharedProjectURL() {
|
||||||
|
return repositoryInfo.getURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getLocalProjectURL() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,8 +71,9 @@ public class TransientProjectManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransientProjectManager() {
|
private TransientProjectManager() {
|
||||||
Runtime.getRuntime().addShutdownHook(
|
Runtime.getRuntime()
|
||||||
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook"));
|
.addShutdownHook(new Thread((Runnable) () -> dispose(),
|
||||||
|
"TransientProjectManager Shutdown Hook"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,8 +81,6 @@ public class TransientProjectManager {
|
||||||
* connections. WARNING: This method intended for testing only.
|
* connections. WARNING: This method intended for testing only.
|
||||||
*/
|
*/
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
// TODO: server handles may be shared with non-transient projects
|
|
||||||
|
|
||||||
TransientProjectData[] projectDataArray =
|
TransientProjectData[] projectDataArray =
|
||||||
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
|
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
|
||||||
for (TransientProjectData projectData : projectDataArray) {
|
for (TransientProjectData projectData : projectDataArray) {
|
||||||
|
|
|
@ -15,17 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.framework.task;
|
package ghidra.framework.task;
|
||||||
|
|
||||||
import generic.concurrent.GThreadPool;
|
|
||||||
import ghidra.framework.model.DomainObjectClosedListener;
|
|
||||||
import ghidra.framework.model.UndoableDomainObject;
|
|
||||||
import ghidra.util.Msg;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import generic.concurrent.GThreadPool;
|
||||||
|
import ghidra.framework.model.*;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for managing a queue of tasks to be executed, one at a time, in priority order. All the
|
* Class for managing a queue of tasks to be executed, one at a time, in priority order. All the
|
||||||
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
|
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
|
||||||
|
@ -95,7 +94,8 @@ public class GTaskManager {
|
||||||
|
|
||||||
domainObject.addCloseListener(new DomainObjectClosedListener() {
|
domainObject.addCloseListener(new DomainObjectClosedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void domainObjectClosed() {
|
public void domainObjectClosed(DomainObject dobj) {
|
||||||
|
// assert dobj == domainObj
|
||||||
GTaskManagerFactory.domainObjectClosed(domainObject);
|
GTaskManagerFactory.domainObjectClosed(domainObject);
|
||||||
domainObject = null;
|
domainObject = null;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ public class GTaskManager {
|
||||||
*
|
*
|
||||||
* @param task the task to be run.
|
* @param task the task to be run.
|
||||||
* @param priority the priority of the task. Lower numbers are run before higher numbers.
|
* @param priority the priority of the task. Lower numbers are run before higher numbers.
|
||||||
* @param useCurrentGroup. If true, this task will be rolled into the current transaction group
|
* @param useCurrentGroup If true, this task will be rolled into the current transaction group
|
||||||
* if one exists. If false, any open transaction
|
* if one exists. If false, any open transaction
|
||||||
* will be closed and a new transaction will be opened before
|
* will be closed and a new transaction will be opened before
|
||||||
* this task is run.
|
* this task is run.
|
||||||
|
@ -680,7 +680,8 @@ public class GTaskManager {
|
||||||
taskListener.taskCompleted(task, result);
|
taskListener.taskCompleted(task, result);
|
||||||
}
|
}
|
||||||
catch (Throwable unexpected) {
|
catch (Throwable unexpected) {
|
||||||
Msg.error(this, "Unexpected exception notifying listener of task completed", unexpected);
|
Msg.error(this, "Unexpected exception notifying listener of task completed",
|
||||||
|
unexpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,7 +706,8 @@ public class GTaskManager {
|
||||||
taskListener.taskScheduled(scheduledTask);
|
taskListener.taskScheduled(scheduledTask);
|
||||||
}
|
}
|
||||||
catch (Throwable unexpected) {
|
catch (Throwable unexpected) {
|
||||||
Msg.error(this, "Unexpected exception notifying listener of task scheduled", unexpected);
|
Msg.error(this, "Unexpected exception notifying listener of task scheduled",
|
||||||
|
unexpected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.framework.model;
|
package ghidra.framework.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ghidra.framework.client.RepositoryAdapter;
|
import ghidra.framework.client.RepositoryAdapter;
|
||||||
|
@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getSharedProjectURL() {
|
||||||
|
// stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getLocalProjectURL() {
|
||||||
|
// stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
|
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
|
||||||
// stub
|
// stub
|
||||||
|
|
|
@ -36,7 +36,7 @@ import docking.wizard.WizardPanel;
|
||||||
import generic.theme.GThemeDefaults.Colors;
|
import generic.theme.GThemeDefaults.Colors;
|
||||||
import ghidra.app.plugin.core.archive.RestoreDialog;
|
import ghidra.app.plugin.core.archive.RestoreDialog;
|
||||||
import ghidra.framework.data.GhidraFileData;
|
import ghidra.framework.data.GhidraFileData;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.main.*;
|
import ghidra.framework.main.*;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.dialog.*;
|
import ghidra.framework.plugintool.dialog.*;
|
||||||
|
@ -694,7 +694,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||||
|
|
||||||
Project project = env.getProject();
|
Project project = env.getProject();
|
||||||
program = env.getProgram("WinHelloCPP.exe");
|
program = env.getProgram("WinHelloCPP.exe");
|
||||||
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
|
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||||
projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY);
|
projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
// Create other project to be viewed
|
// Create other project to be viewed
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.junit.*;
|
||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import generic.test.TestUtils;
|
import generic.test.TestUtils;
|
||||||
import ghidra.framework.data.ProjectFileManager;
|
import ghidra.framework.data.DefaultProjectData;
|
||||||
import ghidra.framework.model.*;
|
import ghidra.framework.model.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.store.ItemCheckoutStatus;
|
import ghidra.framework.store.ItemCheckoutStatus;
|
||||||
|
@ -276,7 +276,7 @@ public class ProjectInfoFilesystemTest extends AbstractGhidraHeadedIntegrationTe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkProjectInfo(Class<?> filesystemClass, String storageType) {
|
private void checkProjectInfo(Class<?> filesystemClass, String storageType) {
|
||||||
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
|
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||||
String msg = "Expected " + filesystemClass.getSimpleName() + ": ";
|
String msg = "Expected " + filesystemClass.getSimpleName() + ": ";
|
||||||
assertTrue(msg + "Local FileSystem", filesystemClass.isInstance(
|
assertTrue(msg + "Local FileSystem", filesystemClass.isInstance(
|
||||||
TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData)));
|
TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData)));
|
||||||
|
|
|
@ -802,9 +802,9 @@ public class ServerTestUtil {
|
||||||
public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name,
|
public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name,
|
||||||
String folderPath, Program program) throws Exception {
|
String folderPath, Program program) throws Exception {
|
||||||
|
|
||||||
ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(program);
|
ContentHandler<?> contentHandler = DomainObjectAdapter.getContentHandler(program);
|
||||||
long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name,
|
long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, program,
|
||||||
program, TaskMonitor.DUMMY);
|
TaskMonitor.DUMMY);
|
||||||
LocalFolderItem item = repoFilesystem.getItem(folderPath, name);
|
LocalFolderItem item = repoFilesystem.getItem(folderPath, name);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
throw new IOException("Item not found: " + FileSystem.SEPARATOR + name);
|
throw new IOException("Item not found: " + FileSystem.SEPARATOR + name);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue