mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-25 05:32:14 +00: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
@ -23,7 +23,7 @@ import org.jdom.Element;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.app.services.TraceRecorder;
|
||||
import ghidra.dbg.target.TargetObject;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@ -664,7 +664,7 @@ public class DebuggerCoordinates {
|
||||
if (projData == null) {
|
||||
try {
|
||||
// FIXME! orphaned instance - transient in nature
|
||||
projData = new ProjectFileManager(projLoc, false, false);
|
||||
projData = new DefaultProjectData(projLoc, false, false);
|
||||
}
|
||||
catch (NotOwnerException e) {
|
||||
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.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.model.DomainObjectClosedListener;
|
||||
import ghidra.trace.model.*;
|
||||
import ghidra.trace.model.Trace.TraceObjectChangeType;
|
||||
@ -45,7 +46,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectClosed() {
|
||||
public void domainObjectClosed(DomainObject dobj) {
|
||||
setTrace(null);
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectClosed() {
|
||||
public void domainObjectClosed(DomainObject dobj) {
|
||||
// assume dobj == program
|
||||
dispose();
|
||||
}
|
||||
|
||||
@ -353,9 +354,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeAdapter {
|
||||
* trace, or bogus external libraries in a mapped program, scoring libraries before module
|
||||
* names should not cause problems.
|
||||
*/
|
||||
Comparator<IndexEntry> comparator = byIsLibrary
|
||||
.thenComparing(byNameSource)
|
||||
.thenComparing(byFolderUses);
|
||||
Comparator<IndexEntry> comparator =
|
||||
byIsLibrary.thenComparing(byNameSource).thenComparing(byFolderUses);
|
||||
return projectData.getFileByID(entries.stream().max(comparator).get().dfID);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,16 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.local.LocalFolderItem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
@ -31,8 +30,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||
|
||||
Project project = state.getProject();
|
||||
|
||||
ProjectFileManager fileMgr = (ProjectFileManager) project.getProjectData();
|
||||
LocalFileSystem fs = (LocalFileSystem) fileMgr.getPrivateFileSystem();
|
||||
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||
LocalFileSystem fs = (LocalFileSystem) projectData.getPrivateFileSystem();
|
||||
|
||||
int cnt = cleanupFolder(fs, "/");
|
||||
|
||||
@ -61,9 +60,8 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||
}
|
||||
|
||||
// fs.getItemNames(folderPath, true)
|
||||
String[] itemNames =
|
||||
(String[]) invokeInstanceMethod("getItemNames", fs, new Class[] { String.class,
|
||||
boolean.class }, new Object[] { folderPath, true });
|
||||
String[] itemNames = (String[]) invokeInstanceMethod("getItemNames", fs,
|
||||
new Class[] { String.class, boolean.class }, new Object[] { folderPath, true });
|
||||
|
||||
for (String itemName : itemNames) {
|
||||
if (!itemName.startsWith(LocalFileSystem.HIDDEN_ITEM_PREFIX)) {
|
||||
@ -78,8 +76,9 @@ public class CleanupMergeDatabasesScript extends GhidraScript {
|
||||
else {
|
||||
// make sure we get item out of index
|
||||
//fs.deallocateItemStorage(folderPath, itemName);
|
||||
invokeInstanceMethod("deallocateItemStorage", fs, new Class[] { String.class,
|
||||
String.class }, new Object[] { folderPath, itemName });
|
||||
invokeInstanceMethod("deallocateItemStorage", fs,
|
||||
new Class[] { String.class, String.class },
|
||||
new Object[] { folderPath, itemName });
|
||||
}
|
||||
++cnt;
|
||||
}
|
||||
|
@ -75,7 +75,16 @@ public class AskScript extends GhidraScript {
|
||||
}
|
||||
|
||||
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?");
|
||||
println("Domain file: " + domFile.getName());
|
||||
|
@ -38,19 +38,24 @@ public class CompareAnalysisScript extends GhidraScript {
|
||||
if (otherProgram == null) {
|
||||
return;
|
||||
}
|
||||
println("\n\n****** COMPARING FUNCTIONS:\n");
|
||||
compareFunctions(otherProgram);
|
||||
println("\n\n****** COMPARING STRINGS:\n");
|
||||
compareStrings(otherProgram);
|
||||
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
|
||||
reportPercentDisassembled(currentProgram);
|
||||
reportPercentDisassembled(otherProgram);
|
||||
println("\n\n****** COMPARING SWITCH TABLES:\n");
|
||||
compareSwitchTables(otherProgram);
|
||||
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
|
||||
compareNoReturns(otherProgram);
|
||||
println("\n\n****** COMPARING ERRORS:\n");
|
||||
compareErrors(otherProgram);
|
||||
try {
|
||||
println("\n\n****** COMPARING FUNCTIONS:\n");
|
||||
compareFunctions(otherProgram);
|
||||
println("\n\n****** COMPARING STRINGS:\n");
|
||||
compareStrings(otherProgram);
|
||||
println("\n\n****** PERCENT ANALYZED COMPARE SUMMARY:\n");
|
||||
reportPercentDisassembled(currentProgram);
|
||||
reportPercentDisassembled(otherProgram);
|
||||
println("\n\n****** COMPARING SWITCH TABLES:\n");
|
||||
compareSwitchTables(otherProgram);
|
||||
println("\n\n****** COMPARING NON-RETURNING FUNCTIONS:\n");
|
||||
compareNoReturns(otherProgram);
|
||||
println("\n\n****** COMPARING ERRORS:\n");
|
||||
compareErrors(otherProgram);
|
||||
}
|
||||
finally {
|
||||
otherProgram.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
void compareFunctions(Program otherProgram) {
|
||||
|
@ -73,7 +73,7 @@ public class AnalysisStateInfo {
|
||||
if (stateMap == null) {
|
||||
stateMap = new HashMap<>();
|
||||
programStates.put(program, stateMap);
|
||||
program.addCloseListener(() -> programStates.remove(program));
|
||||
program.addCloseListener(doa -> programStates.remove(program));
|
||||
}
|
||||
stateMap.put(state.getClass(), state);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ import ghidra.util.task.*;
|
||||
* Provides support for auto analysis tasks.
|
||||
* 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.
|
||||
@ -145,7 +145,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||
private AutoAnalysisManager(Program program) {
|
||||
this.program = program;
|
||||
eventQueueID = program.createPrivateEventQueue(this, 500);
|
||||
program.addCloseListener(this);
|
||||
program.addCloseListener(dobj -> dispose());
|
||||
initializeAnalyzers();
|
||||
}
|
||||
|
||||
@ -361,11 +361,6 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||
subType == ChangeManager.FUNCTION_CHANGED_RETURN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectClosed() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
if (program == null) {
|
||||
@ -961,10 +956,7 @@ public class AutoAnalysisManager implements DomainObjectListener, DomainObjectCl
|
||||
}
|
||||
|
||||
PluginTool anyTool = null;
|
||||
Iterator<PluginTool> iterator = toolSet.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
PluginTool tool = iterator.next();
|
||||
|
||||
for (PluginTool tool : toolSet) {
|
||||
anyTool = tool;
|
||||
JFrame toolFrame = tool.getToolFrame();
|
||||
if (toolFrame != null && toolFrame.isActive()) {
|
||||
|
@ -33,7 +33,6 @@ import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.viewer.listingpanel.*;
|
||||
import ghidra.app.util.viewer.util.AddressIndexMap;
|
||||
import ghidra.framework.model.DomainObjectClosedListener;
|
||||
import ghidra.framework.plugintool.Plugin;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
@ -510,30 +509,19 @@ public class MarkerManager implements MarkerService {
|
||||
private final AddressColorCache colorCache = new AddressColorCache();
|
||||
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) {
|
||||
this.cache = cache;
|
||||
this.program = program;
|
||||
/**
|
||||
* 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
|
||||
* domain object closing, which works for plain programs, too.
|
||||
*/
|
||||
program.addCloseListener(closeListener);
|
||||
program.addCloseListener(dobj -> cache.programClosed(program));
|
||||
}
|
||||
|
||||
void clearColors() {
|
||||
colorCache.clear();
|
||||
}
|
||||
|
||||
private void programClosed() {
|
||||
program.removeCloseListener(closeListener);
|
||||
cache.programClosed(program);
|
||||
}
|
||||
|
||||
MarkerSetImpl getByName(String name) {
|
||||
for (MarkerSetImpl set : markerSets) {
|
||||
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 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
|
||||
* @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) {
|
||||
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
|
||||
* value can be found in script args or a properties file
|
||||
* @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
|
||||
*
|
||||
* @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) {
|
||||
|
||||
@ -2513,7 +2517,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||
public Address askAddress(String title, String message) throws CancelledException {
|
||||
return askAddress(title, message, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an Address, using the String parameters for guidance. The actual behavior of the
|
||||
* method depends on your environment, which can be GUI or headless.
|
||||
@ -2550,15 +2554,16 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||
* @throws IllegalArgumentException if in headless mode, there was a missing or invalid Address
|
||||
* 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);
|
||||
|
||||
|
||||
Address defaultAddr = null;
|
||||
if (defaultValue != null) {
|
||||
defaultAddr = currentProgram.getAddressFactory().getAddress(defaultValue);
|
||||
}
|
||||
|
||||
|
||||
// if defaultAddr is null then it assumes no default value
|
||||
Address existingValue = loadAskValue(defaultAddr, this::parseAddress, key);
|
||||
if (isRunningHeadless()) {
|
||||
@ -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
|
||||
* 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 IOException if there is an error accessing the Program's DomainObject
|
||||
* @throws CancelledException if the operation is cancelled
|
||||
@ -2693,33 +2703,34 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||
public Program askProgram(String title)
|
||||
throws VersionException, IOException, CancelledException {
|
||||
|
||||
DomainFile existingValue = loadAskValue(this::parseDomainFile, title);
|
||||
if (isRunningHeadless()) {
|
||||
return (Program) existingValue.getDomainObject(this, false, false, monitor);
|
||||
DomainFile choice = loadAskValue(this::parseDomainFile, title);
|
||||
if (!isRunningHeadless()) {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Program p = (Program) choice.getDomainObject(this, false, false, monitor);
|
||||
|
||||
PluginTool tool = state.getTool();
|
||||
if (tool == null) {
|
||||
return (Program) choice.getDomainObject(this, false, false, monitor);
|
||||
return p;
|
||||
}
|
||||
|
||||
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
|
||||
* 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
|
||||
* @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 {
|
||||
|
||||
@ -3015,8 +3026,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||
throw new ImproperUseException(
|
||||
"The askPassword() method can only be used when running headed Ghidra.");
|
||||
}
|
||||
PasswordDialog dialog =
|
||||
new PasswordDialog(title, null, null, prompt, null, null);
|
||||
PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null);
|
||||
try {
|
||||
state.getTool().showDialog(dialog);
|
||||
if (!dialog.okWasPressed()) {
|
||||
|
@ -878,7 +878,7 @@ public class HeadlessAnalyzer {
|
||||
|
||||
// Get parent folder to pass to GhidraScript
|
||||
File parentFile = new File(c.getResource(c.getSimpleName() + ".class").toURI())
|
||||
.getParentFile();
|
||||
.getParentFile();
|
||||
|
||||
currScript = (GhidraScript) c.getConstructor().newInstance();
|
||||
currScript.setScriptArgs(scriptArgs);
|
||||
@ -1575,13 +1575,12 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
else {
|
||||
if (options.readOnly) {
|
||||
Msg.info(this, "REPORT: Discarded file import due to readOnly option: " +
|
||||
loaded);
|
||||
Msg.info(this,
|
||||
"REPORT: Discarded file import due to readOnly option: " + loaded);
|
||||
}
|
||||
else {
|
||||
Msg.info(this,
|
||||
"REPORT: Discarded file import as a result of script " +
|
||||
"activity or analysis timeout: " + loaded);
|
||||
Msg.info(this, "REPORT: Discarded file import as a result of script " +
|
||||
"activity or analysis timeout: " + loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1627,9 +1626,9 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
private LoadResults<Program> loadPrograms(File file, String folderPath) throws VersionException,
|
||||
InvalidNameException, DuplicateNameException, CancelledException, IOException,
|
||||
LoadException {
|
||||
private LoadResults<Program> loadPrograms(File file, String folderPath)
|
||||
throws VersionException, InvalidNameException, DuplicateNameException,
|
||||
CancelledException, IOException, LoadException {
|
||||
MessageLog messageLog = new MessageLog();
|
||||
|
||||
if (options.loaderClass == null) {
|
||||
|
@ -28,6 +28,7 @@ import org.junit.*;
|
||||
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.FileSystemEventManager;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
@ -37,13 +38,13 @@ import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public class DefaultProjectDataTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private File privateProjectDir;
|
||||
private File sharedProjectDir;
|
||||
private FileSystem sharedFS;
|
||||
private LocalFileSystem privateFS;
|
||||
private ProjectFileManager fileMgr;
|
||||
private DefaultProjectData projectData;
|
||||
private DomainFolder root;
|
||||
private List<MyEvent> events = new ArrayList<>();
|
||||
|
||||
@ -83,9 +84,9 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||
true, false, true);
|
||||
|
||||
fileMgr = new ProjectFileManager(privateFS, sharedFS);
|
||||
fileMgr.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
|
||||
root = fileMgr.getRootFolder();
|
||||
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||
projectData.addDomainFolderChangeListener(new MyDomainFolderChangeListener());
|
||||
root = projectData.getRootFolder();
|
||||
flushFileSystemEventsAndClearTestQueue();
|
||||
}
|
||||
|
||||
@ -97,7 +98,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
fileMgr.dispose();
|
||||
projectData.dispose();
|
||||
deleteAll(privateProjectDir);
|
||||
deleteAll(sharedProjectDir);
|
||||
}
|
||||
@ -136,9 +137,15 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLocalProjectURL() {
|
||||
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator()),
|
||||
projectData.getLocalProjectURL());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRootFolder() throws Exception {
|
||||
DomainFolder rootFolder = fileMgr.getRootFolder();
|
||||
DomainFolder rootFolder = projectData.getRootFolder();
|
||||
assertEquals("/", rootFolder.getPathname());
|
||||
assertEquals(3, rootFolder.getFolders().length);
|
||||
}
|
||||
@ -146,11 +153,11 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
@Test
|
||||
public void testGetFolder() throws Exception {
|
||||
|
||||
DomainFolder rootFolder = fileMgr.getRootFolder();
|
||||
DomainFolder df1 = fileMgr.getFolder("/");
|
||||
DomainFolder df2 = fileMgr.getFolder("/a");
|
||||
DomainFolder df3 = fileMgr.getFolder("/a/y");
|
||||
DomainFolder df4 = fileMgr.getFolder("/a/x");
|
||||
DomainFolder rootFolder = projectData.getRootFolder();
|
||||
DomainFolder df1 = projectData.getFolder("/");
|
||||
DomainFolder df2 = projectData.getFolder("/a");
|
||||
DomainFolder df3 = projectData.getFolder("/a/y");
|
||||
DomainFolder df4 = projectData.getFolder("/a/x");
|
||||
|
||||
assertNotNull(rootFolder);
|
||||
assertEquals(rootFolder, df1);
|
||||
@ -178,7 +185,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
|
||||
@Test
|
||||
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
|
||||
flushFileSystemEventsAndClearTestQueue();
|
||||
|
||||
@ -195,18 +202,18 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
assertEventsSize(2);
|
||||
checkEvent(events.get(1), "File Added", null, null, "/a/file2", null, null);
|
||||
|
||||
DomainFile df = fileMgr.getFileByID(fileID1);
|
||||
DomainFile df = projectData.getFileByID(fileID1);
|
||||
assertNotNull(df);
|
||||
assertEquals("file1", df.getName());
|
||||
assertTrue(!df.isVersioned());
|
||||
|
||||
df = fileMgr.getFileByID(fileID2);
|
||||
df = projectData.getFileByID(fileID2);
|
||||
assertNotNull(df2);
|
||||
assertEquals("file2", df.getName());
|
||||
|
||||
df1.addToVersionControl("", false, TaskMonitor.DUMMY);
|
||||
|
||||
df = fileMgr.getFileByID(fileID1);
|
||||
df = projectData.getFileByID(fileID1);
|
||||
assertNotNull(df1);
|
||||
assertEquals("file1", df.getName());
|
||||
assertTrue(df.isVersioned());
|
||||
@ -216,7 +223,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
@Test
|
||||
public void testFileIndex() throws Exception {
|
||||
|
||||
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", fileMgr);
|
||||
DomainFileIndex fileIndex = (DomainFileIndex) getInstanceField("fileIndex", projectData);
|
||||
assertNotNull(fileIndex);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -224,21 +231,21 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
(HashMap<String, String>) getInstanceField("fileIdToPathIndex", fileIndex);
|
||||
assertNotNull(fileIdToPathIndex);
|
||||
|
||||
DomainFolder folder = fileMgr.getFolder("/a");
|
||||
DomainFolder folder = projectData.getFolder("/a");
|
||||
|
||||
DomainFile df1 = createFile(folder, "file1");
|
||||
String fileID = df1.getFileID();
|
||||
|
||||
assertEquals(df1, fileMgr.getFileByID(fileID));
|
||||
assertEquals(df1, projectData.getFileByID(fileID));
|
||||
|
||||
// invalidate folder data to force search
|
||||
|
||||
GhidraFolderData rootFolderData = fileMgr.getRootFolderData();
|
||||
GhidraFolderData rootFolderData = projectData.getRootFolderData();
|
||||
rootFolderData.dispose();
|
||||
|
||||
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
|
||||
}
|
||||
@ -246,7 +253,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
@Test
|
||||
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
|
||||
DomainFolder folder = fileMgr.getFolder("/a");
|
||||
DomainFolder folder = projectData.getFolder("/a");
|
||||
|
||||
DomainFile df1 = createFile(folder, "file1");
|
||||
String fileID = df1.getFileID();
|
||||
@ -265,7 +272,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
@Test
|
||||
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
|
||||
DomainFolder folder = fileMgr.getFolder("/a");
|
||||
DomainFolder folder = projectData.getFolder("/a");
|
||||
folder.getFiles(); // visit folder to enable folder change listener
|
||||
|
||||
// create shared file /a/file1 and keep checked-out
|
||||
@ -285,7 +292,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
|
||||
df1.setName("file2");
|
||||
|
||||
DomainFile df2 = fileMgr.getFile("/a/file2");
|
||||
DomainFile df2 = projectData.getFile("/a/file2");
|
||||
|
||||
assertTrue(!fileID.equals(df2.getFileID()));
|
||||
|
||||
@ -451,7 +458,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
|
||||
@Test
|
||||
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();
|
||||
|
||||
// exists in localFS so "b" folder should not get created again
|
||||
@ -495,7 +502,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
|
||||
|
||||
@Test
|
||||
public void testRenameFolder6() throws Exception {
|
||||
DomainFolder aFolder = fileMgr.getFolder("/a");
|
||||
DomainFolder aFolder = projectData.getFolder("/a");
|
||||
assertNotNull(aFolder);
|
||||
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
|
||||
|
||||
GhidraFolder folder = (GhidraFolder) fileMgr.getFolder("/a");
|
||||
GhidraFolder folder = (GhidraFolder) projectData.getFolder("/a");
|
||||
assertNotNull(folder);
|
||||
assertTrue(folder.privateExists());
|
||||
assertFalse(folder.sharedExists());
|
||||
|
||||
folder = (GhidraFolder) fileMgr.getFolder("/c/a");
|
||||
folder = (GhidraFolder) projectData.getFolder("/c/a");
|
||||
assertNotNull(folder);
|
||||
assertFalse(folder.privateExists());
|
||||
assertTrue(folder.sharedExists());
|
@ -41,7 +41,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private FileSystem sharedFS;
|
||||
private LocalFileSystem privateFS;
|
||||
|
||||
private ProjectFileManager pfm;
|
||||
private DefaultProjectData projectData;
|
||||
private GhidraFolder root;
|
||||
|
||||
public GhidraFileTest() {
|
||||
@ -76,8 +76,8 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
false, false, true);
|
||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||
true, false, true);
|
||||
pfm = new ProjectFileManager(privateFS, sharedFS);
|
||||
root = pfm.getRootFolder();
|
||||
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||
root = projectData.getRootFolder();
|
||||
|
||||
}
|
||||
|
||||
@ -88,12 +88,12 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalURL() throws IOException {
|
||||
public void testGetLocalProjectURL() throws IOException {
|
||||
createDB(privateFS, "/a", "file1");
|
||||
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"),
|
||||
pfm.getFile("/a/file1").getLocalProjectURL("xyz"));
|
||||
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null),
|
||||
pfm.getFile("/a/file1").getLocalProjectURL(null));
|
||||
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", "xyz"),
|
||||
projectData.getFile("/a/file1").getLocalProjectURL("xyz"));
|
||||
assertEquals(GhidraURL.makeURL(projectData.getProjectLocator(), "/a/file1", null),
|
||||
projectData.getFile("/a/file1").getLocalProjectURL(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -345,7 +345,7 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private void refresh() throws IOException {
|
||||
// refresh everything regardless of visited state
|
||||
pfm.refresh(true);
|
||||
projectData.refresh(true);
|
||||
}
|
||||
|
||||
private void deleteAll(File file) {
|
||||
|
@ -22,6 +22,8 @@ import java.io.IOException;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
|
||||
@ -31,6 +33,7 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private LocalFileSystem sharedFS;
|
||||
private LocalFileSystem privateFS;
|
||||
|
||||
private DefaultProjectData projectData;
|
||||
private GhidraFolder root;
|
||||
|
||||
public GhidraFolderTest() {
|
||||
@ -68,8 +71,8 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
false, false, true);
|
||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), false,
|
||||
true, false, true);
|
||||
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
|
||||
root = projectFileManager.getRootFolder();
|
||||
projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||
root = projectData.getRootFolder();
|
||||
}
|
||||
|
||||
private void deleteTestFiles() {
|
||||
@ -82,6 +85,15 @@ public class GhidraFolderTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
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
|
||||
public void testGetFolderNames() throws Exception {
|
||||
GhidraFolder[] folders = root.getFolders();
|
||||
|
@ -15,24 +15,23 @@
|
||||
*/
|
||||
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.IOException;
|
||||
|
||||
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 {
|
||||
|
||||
private File testRootDir;
|
||||
private File privateProjectDir;
|
||||
private File sharedProjectDir;
|
||||
private DomainFolder root;
|
||||
private Project project;
|
||||
|
||||
private LocalFileSystem sharedFS;
|
||||
private LocalFileSystem privateFS;
|
||||
|
||||
@ -52,8 +51,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
|
||||
true, false, false);
|
||||
sharedFS = LocalFileSystem.getLocalFileSystem(sharedProjectDir.getAbsolutePath(), true,
|
||||
true, false, false);
|
||||
ProjectFileManager projectFileManager = new ProjectFileManager(privateFS, sharedFS);
|
||||
root = projectFileManager.getRootFolder();
|
||||
DefaultProjectData projectData = new DefaultProjectData(privateFS, sharedFS);
|
||||
root = projectData.getRootFolder();
|
||||
}
|
||||
|
||||
@After
|
||||
@ -68,8 +67,8 @@ public class IndexedFileSystemFolderTest extends AbstractGhidraHeadedIntegration
|
||||
private void deleteAll(File file) {
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
deleteAll(files[i]);
|
||||
for (File file2 : files) {
|
||||
deleteAll(file2);
|
||||
}
|
||||
}
|
||||
file.delete();
|
||||
|
@ -903,8 +903,8 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||
//
|
||||
|
||||
//@formatter:off
|
||||
Object projectFileManager = getInstanceField("fileManager", df);
|
||||
invokeInstanceMethod("setDomainObject", projectFileManager,
|
||||
Object projectData = getInstanceField("projectData", df);
|
||||
invokeInstanceMethod("setDomainObject", projectData,
|
||||
new Class[] { String.class, DomainObjectAdapter.class },
|
||||
new Object[] { path, program }
|
||||
);
|
||||
@ -962,8 +962,7 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
||||
}
|
||||
}
|
||||
|
||||
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree,
|
||||
true);
|
||||
return new FrontEndProjectTreeContext(null, null, paths, folderList, fileList, tree, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,16 +15,18 @@
|
||||
*/
|
||||
package ghidra.framework.project;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
|
||||
import ghidra.test.ProjectTestUtils;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
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_VIEW1 = "TestView1";
|
||||
private final static String PROJECT_VIEW2 = "TestView2";
|
||||
@ -52,24 +54,9 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
|
||||
ProjectTestUtils.deleteProject(DIRECTORY_NAME, PROJECT_VIEW2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the test.
|
||||
* @param args same args that are passed to RegressionTester.main()
|
||||
*/
|
||||
@Test
|
||||
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...
|
||||
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW1).close();
|
||||
ProjectTestUtils.getProject(DIRECTORY_NAME, PROJECT_VIEW2).close();
|
||||
@ -87,12 +74,12 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
|
||||
// validate the view was added to project
|
||||
ProjectLocator[] projViews = project.getProjectViews();
|
||||
for (ProjectLocator projView : projViews) {
|
||||
System.out.println("added view: " + projView);
|
||||
Msg.debug(this, "** added view: " + projView);
|
||||
}
|
||||
|
||||
// remove the view...
|
||||
project.removeProjectView(view);
|
||||
System.out.println("removed view: " + view);
|
||||
Msg.debug(this, "** removed view: " + view);
|
||||
|
||||
projViews = project.getProjectViews();
|
||||
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 generic.test.AbstractGTest;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
@ -57,8 +57,8 @@ public class ProgramUserDataTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
projectLocator = new ProjectLocator(TEMP, "Test");
|
||||
project = TestProjectManager.get().createProject(projectLocator, null, true);
|
||||
dataDir =
|
||||
new File(projectLocator.getProjectDir(), ProjectFileManager.INDEXED_DATA_FOLDER_NAME);
|
||||
userDir = new File(projectLocator.getProjectDir(), ProjectFileManager.USER_FOLDER_NAME);
|
||||
new File(projectLocator.getProjectDir(), DefaultProjectData.INDEXED_DATA_FOLDER_NAME);
|
||||
userDir = new File(projectLocator.getProjectDir(), DefaultProjectData.USER_FOLDER_NAME);
|
||||
|
||||
ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY);
|
||||
df = project.getProjectData()
|
||||
|
@ -81,8 +81,8 @@ public class FakeSharedProject {
|
||||
// Note: this how we share multiple projects
|
||||
void setVersionedFileSystem(LocalFileSystem fs) {
|
||||
|
||||
ProjectFileManager fm = getProjectFileManager();
|
||||
invokeInstanceMethod("setVersionedFileSystem", fm, argTypes(FileSystem.class), args(fs));
|
||||
DefaultProjectData pd = getProjectData();
|
||||
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() {
|
||||
return (ProjectFileManager) gProject.getProjectData();
|
||||
public DefaultProjectData getProjectData() {
|
||||
return (DefaultProjectData) gProject.getProjectData();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,8 +108,8 @@ public class FakeSharedProject {
|
||||
* @return the root folder of this project
|
||||
*/
|
||||
public RootGhidraFolder getRootFolder() {
|
||||
ProjectFileManager pfm = getProjectFileManager();
|
||||
return (RootGhidraFolder) pfm.getRootFolder();
|
||||
DefaultProjectData pd = getProjectData();
|
||||
return (RootGhidraFolder) pd.getRootFolder();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,6 +181,7 @@ public class FakeSharedProject {
|
||||
* <li>calling {@link #addDomainFile(String)}</li>
|
||||
* <li>Adding a versioned file to another project that shares the same repo with this project</li>
|
||||
* </ul>
|
||||
* @param parentPath the parent folder path
|
||||
* @param filename the filename
|
||||
* @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
|
||||
* folder if needed
|
||||
*
|
||||
* @param parentPath the parent folder path
|
||||
* @param name the name of the folder to create
|
||||
* @param path the full path of the folder to create
|
||||
* @return the created folder
|
||||
* @throws Exception if there are any exceptions creating the folder
|
||||
*/
|
||||
@ -367,7 +367,7 @@ public class FakeSharedProject {
|
||||
* @see FakeRepository#dispose()
|
||||
*/
|
||||
public void dispose() {
|
||||
ProjectLocator projectLocator = getProjectFileManager().getProjectLocator();
|
||||
ProjectLocator projectLocator = getProjectData().getProjectLocator();
|
||||
programManager.disposeOpenPrograms();
|
||||
gProject.close();
|
||||
FileUtilities.deleteDir(projectLocator.getProjectDir());
|
||||
@ -388,7 +388,7 @@ public class FakeSharedProject {
|
||||
}
|
||||
|
||||
ProjectLocator pl = df.getProjectLocator();
|
||||
ProjectLocator mypl = getProjectFileManager().getProjectLocator();
|
||||
ProjectLocator mypl = getProjectData().getProjectLocator();
|
||||
if (!pl.equals(mypl)) {
|
||||
throw new IllegalArgumentException("Domain file '" + df + "' is not in this project: " +
|
||||
mypl.getName() + "\nYou must call addDomainFile(filename).");
|
||||
@ -397,9 +397,8 @@ public class FakeSharedProject {
|
||||
|
||||
private void waitForFileSystemEvents() {
|
||||
LocalFileSystem versionedFileSystem = getVersionedFileSystem();
|
||||
FileSystemEventManager eventManager =
|
||||
(FileSystemEventManager) TestUtils.getInstanceField("eventManager",
|
||||
versionedFileSystem);
|
||||
FileSystemEventManager eventManager = (FileSystemEventManager) TestUtils
|
||||
.getInstanceField("eventManager", versionedFileSystem);
|
||||
|
||||
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
@ -444,16 +443,16 @@ public class FakeSharedProject {
|
||||
}
|
||||
|
||||
LocalFileSystem getVersionedFileSystem() {
|
||||
ProjectFileManager fileManager = getProjectFileManager();
|
||||
DefaultProjectData projectData = getProjectData();
|
||||
LocalFileSystem fs =
|
||||
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", fileManager);
|
||||
(LocalFileSystem) TestUtils.invokeInstanceMethod("getVersionedFileSystem", projectData);
|
||||
return fs;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
ProjectFileManager fileManager = getProjectFileManager();
|
||||
DefaultProjectData projectData = getProjectData();
|
||||
try {
|
||||
fileManager.refresh(true);
|
||||
projectData.refresh(true);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// shouldn't happen
|
||||
|
@ -33,215 +33,219 @@ import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
|
||||
|
||||
public class MergeTwoProgramsScript extends GhidraScript {
|
||||
|
||||
@Override
|
||||
protected void run() throws Exception {
|
||||
|
||||
if ( currentProgram == null ) {
|
||||
printerr( "Please open a program first!" );
|
||||
if (currentProgram == null) {
|
||||
printerr("Please open a program first!");
|
||||
return;
|
||||
}
|
||||
|
||||
Program otherProgram = askProgram( "Select program from which to merge: " );
|
||||
Program otherProgram = askProgram("Select program from which to merge: ");
|
||||
|
||||
if ( otherProgram == null ) {
|
||||
printerr( "Please select the other program first!" );
|
||||
if (otherProgram == null) {
|
||||
printerr("Please select the other program first!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !currentProgram.getLanguage().equals( otherProgram.getLanguage() ) ) {
|
||||
printerr( "Incompatible program languages!" );
|
||||
return;
|
||||
try {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if ( currentProgram.getMemory().intersects( otherProgram.getMemory() ) ) {
|
||||
printerr( "Memory map of current program must be disjoint from other program!" );
|
||||
return;
|
||||
finally {
|
||||
otherProgram.release(this);
|
||||
}
|
||||
|
||||
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 ) {
|
||||
monitor.setMessage( "Merging references..." );
|
||||
private void mergeReferences(Program currProgram, Program otherProgram) {
|
||||
monitor.setMessage("Merging references...");
|
||||
ReferenceManager currentReferenceManager = currProgram.getReferenceManager();
|
||||
ReferenceManager otherReferenceManager = otherProgram.getReferenceManager();
|
||||
ReferenceIterator otherReferenceIterator = otherReferenceManager.getReferenceIterator( otherProgram.getMinAddress() );
|
||||
while ( otherReferenceIterator.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
ReferenceIterator otherReferenceIterator =
|
||||
otherReferenceManager.getReferenceIterator(otherProgram.getMinAddress());
|
||||
while (otherReferenceIterator.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Reference otherReference = otherReferenceIterator.next();
|
||||
if ( otherReference.isStackReference() ) {
|
||||
if (otherReference.isStackReference()) {
|
||||
continue;
|
||||
}
|
||||
currentReferenceManager.addReference( otherReference );
|
||||
currentReferenceManager.addReference(otherReference);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeInstructions( Program currProgram, Program otherProgram ) {
|
||||
monitor.setMessage( "Merging instructions..." );
|
||||
private void mergeInstructions(Program currProgram, Program otherProgram) {
|
||||
monitor.setMessage("Merging instructions...");
|
||||
Listing currentListing = currProgram.getListing();
|
||||
Listing otherListing = otherProgram.getListing();
|
||||
InstructionIterator otherInstructions = otherListing.getInstructions( true );
|
||||
while ( otherInstructions.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
InstructionIterator otherInstructions = otherListing.getInstructions(true);
|
||||
while (otherInstructions.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Instruction otherInstruction = otherInstructions.next();
|
||||
if ( currentListing.isUndefined( otherInstruction.getMinAddress(), otherInstruction.getMaxAddress() ) ) {
|
||||
disassemble( otherInstruction.getMinAddress() );
|
||||
if (currentListing.isUndefined(otherInstruction.getMinAddress(),
|
||||
otherInstruction.getMaxAddress())) {
|
||||
disassemble(otherInstruction.getMinAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeEquates( Program currProgram, Program otherProgram ) throws Exception {
|
||||
monitor.setMessage( "Merging equates..." );
|
||||
private void mergeEquates(Program currProgram, Program otherProgram) throws Exception {
|
||||
monitor.setMessage("Merging equates...");
|
||||
EquateTable currentEquateTable = currProgram.getEquateTable();
|
||||
EquateTable otherEquateTable = otherProgram.getEquateTable();
|
||||
Iterator<Equate> otherEquates = otherEquateTable.getEquates();
|
||||
while ( otherEquates.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
while (otherEquates.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Equate otherEquate = otherEquates.next();
|
||||
Equate currentEquate = currentEquateTable.createEquate( otherEquate.getName(), otherEquate.getValue() );
|
||||
EquateReference [] otherEquateReferences = otherEquate.getReferences();
|
||||
for ( EquateReference otherEquateReference : otherEquateReferences ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
Equate currentEquate =
|
||||
currentEquateTable.createEquate(otherEquate.getName(), otherEquate.getValue());
|
||||
EquateReference[] otherEquateReferences = otherEquate.getReferences();
|
||||
for (EquateReference otherEquateReference : otherEquateReferences) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
currentEquate.addReference( otherEquateReference.getAddress(), otherEquateReference.getOpIndex() );
|
||||
currentEquate.addReference(otherEquateReference.getAddress(),
|
||||
otherEquateReference.getOpIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeData( Program currProgram, Program otherProgram ) throws Exception {
|
||||
monitor.setMessage( "Merging data..." );
|
||||
private void mergeData(Program currProgram, Program otherProgram) throws Exception {
|
||||
monitor.setMessage("Merging data...");
|
||||
Listing currentListing = currProgram.getListing();
|
||||
Listing otherListing = otherProgram.getListing();
|
||||
DataIterator otherDataIterator = otherListing.getDefinedData( true );
|
||||
while ( otherDataIterator.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
DataIterator otherDataIterator = otherListing.getDefinedData(true);
|
||||
while (otherDataIterator.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Data otherData = otherDataIterator.next();
|
||||
if ( currentListing.isUndefined( otherData.getMinAddress(), otherData.getMaxAddress() ) ) {
|
||||
currentListing.createData( otherData.getMinAddress(), otherData.getDataType() );
|
||||
if (currentListing.isUndefined(otherData.getMinAddress(), otherData.getMaxAddress())) {
|
||||
currentListing.createData(otherData.getMinAddress(), otherData.getDataType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeComments( Program currProgram, Program otherProgram ) throws Exception {
|
||||
monitor.setMessage( "Merging comments..." );
|
||||
int [] commentTypes = {
|
||||
CodeUnit.EOL_COMMENT,
|
||||
CodeUnit.PRE_COMMENT,
|
||||
CodeUnit.POST_COMMENT,
|
||||
CodeUnit.PLATE_COMMENT,
|
||||
CodeUnit.REPEATABLE_COMMENT,
|
||||
};
|
||||
private void mergeComments(Program currProgram, Program otherProgram) throws Exception {
|
||||
monitor.setMessage("Merging comments...");
|
||||
int[] commentTypes = { CodeUnit.EOL_COMMENT, CodeUnit.PRE_COMMENT, CodeUnit.POST_COMMENT,
|
||||
CodeUnit.PLATE_COMMENT, CodeUnit.REPEATABLE_COMMENT, };
|
||||
Listing currentListing = currProgram.getListing();
|
||||
Listing otherListing = otherProgram.getListing();
|
||||
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits( true );
|
||||
while ( otherCodeUnits.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
CodeUnitIterator otherCodeUnits = otherListing.getCodeUnits(true);
|
||||
while (otherCodeUnits.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
CodeUnit otherCodeUnit = otherCodeUnits.next();
|
||||
for ( int commentType : commentTypes ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
for (int commentType : commentTypes) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
String otherComment = otherCodeUnit.getComment( commentType );
|
||||
if ( otherComment != null ) {
|
||||
currentListing.setComment( otherCodeUnit.getAddress(), commentType, otherComment );
|
||||
String otherComment = otherCodeUnit.getComment(commentType);
|
||||
if (otherComment != null) {
|
||||
currentListing.setComment(otherCodeUnit.getAddress(), commentType,
|
||||
otherComment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeBookmarks( Program currProgram, Program otherProgram ) {
|
||||
monitor.setMessage( "Merging bookmarks..." );
|
||||
private void mergeBookmarks(Program currProgram, Program otherProgram) {
|
||||
monitor.setMessage("Merging bookmarks...");
|
||||
BookmarkManager currentBookmarkManager = currProgram.getBookmarkManager();
|
||||
BookmarkManager otherBookmarkManager = otherProgram.getBookmarkManager();
|
||||
Iterator<Bookmark> otherBookmarks = otherBookmarkManager.getBookmarksIterator();
|
||||
while ( otherBookmarks.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
while (otherBookmarks.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Bookmark otherBookmark = otherBookmarks.next();
|
||||
currentBookmarkManager.setBookmark( otherBookmark.getAddress(),
|
||||
otherBookmark.getTypeString(),
|
||||
otherBookmark.getCategory(),
|
||||
otherBookmark.getComment() );
|
||||
currentBookmarkManager.setBookmark(otherBookmark.getAddress(),
|
||||
otherBookmark.getTypeString(), otherBookmark.getCategory(),
|
||||
otherBookmark.getComment());
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeSymbols( Program currProgram, Program otherProgram ) throws Exception {
|
||||
monitor.setMessage( "Merging symbols..." );
|
||||
private void mergeSymbols(Program currProgram, Program otherProgram) throws Exception {
|
||||
monitor.setMessage("Merging symbols...");
|
||||
SymbolTable currentSymbolTable = currProgram.getSymbolTable();
|
||||
SymbolTable otherSymbolTable = otherProgram.getSymbolTable();
|
||||
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols( false );
|
||||
while ( otherSymbols.hasNext() ) {
|
||||
if ( monitor.isCancelled() ) {
|
||||
SymbolIterator otherSymbols = otherSymbolTable.getAllSymbols(false);
|
||||
while (otherSymbols.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Symbol otherSymbol = otherSymbols.next();
|
||||
if ( otherSymbol.isDynamic() ) {
|
||||
if (otherSymbol.isDynamic()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Namespace otherNamespace = otherSymbol.getParentNamespace();
|
||||
Namespace currentNamespace = mirrorNamespace( currProgram, otherProgram, otherNamespace );
|
||||
if ( otherSymbol.getSymbolType() == SymbolType.FUNCTION ) {
|
||||
Function otherFunction = otherProgram.getListing().getFunctionAt( otherSymbol.getAddress() );
|
||||
currProgram.getListing().createFunction( otherSymbol.getName(),
|
||||
currentNamespace,
|
||||
otherFunction.getEntryPoint(),
|
||||
otherFunction.getBody(),
|
||||
SourceType.USER_DEFINED );
|
||||
Namespace currentNamespace =
|
||||
mirrorNamespace(currProgram, otherProgram, otherNamespace);
|
||||
if (otherSymbol.getSymbolType() == SymbolType.FUNCTION) {
|
||||
Function otherFunction =
|
||||
otherProgram.getListing().getFunctionAt(otherSymbol.getAddress());
|
||||
currProgram.getListing()
|
||||
.createFunction(otherSymbol.getName(), currentNamespace,
|
||||
otherFunction.getEntryPoint(), otherFunction.getBody(),
|
||||
SourceType.USER_DEFINED);
|
||||
}
|
||||
else {
|
||||
currentSymbolTable.createLabel( otherSymbol.getAddress(),
|
||||
otherSymbol.getName(),
|
||||
currentNamespace,
|
||||
SourceType.USER_DEFINED );
|
||||
currentSymbolTable.createLabel(otherSymbol.getAddress(), otherSymbol.getName(),
|
||||
currentNamespace, SourceType.USER_DEFINED);
|
||||
}
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
printerr( "Unable to create symbol: " + otherSymbol.getName() );
|
||||
catch (Exception e) {
|
||||
printerr("Unable to create symbol: " + otherSymbol.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Namespace mirrorNamespace( Program currProgram, Program otherProgram, Namespace otherNamespace ) throws Exception {
|
||||
if ( otherNamespace == null ) {
|
||||
private Namespace mirrorNamespace(Program currProgram, Program otherProgram,
|
||||
Namespace otherNamespace) throws Exception {
|
||||
if (otherNamespace == null) {
|
||||
return currProgram.getGlobalNamespace();
|
||||
}
|
||||
SourceType source = SourceType.USER_DEFINED;//this will be default, since we are running a script!
|
||||
try {
|
||||
source = otherNamespace.getSymbol().getSource();
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
catch (Exception e) {
|
||||
}
|
||||
return NamespaceUtils.createNamespaceHierarchy(otherNamespace.getName(true), null,
|
||||
currProgram, source);
|
||||
}
|
||||
|
||||
private void mergeMemory( Program currProgram, Program otherProgram ) throws Exception {
|
||||
monitor.setMessage( "Merging memory..." );
|
||||
private void mergeMemory(Program currProgram, Program otherProgram) throws Exception {
|
||||
monitor.setMessage("Merging memory...");
|
||||
Memory otherMemory = otherProgram.getMemory();
|
||||
MemoryBlock[] otherBlocks = otherMemory.getBlocks();
|
||||
MessageLog log = new MessageLog();
|
||||
|
@ -17,7 +17,6 @@
|
||||
// data and and then save the session.
|
||||
//@category Examples.Version Tracking
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
@ -32,6 +31,21 @@ import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
|
||||
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
|
||||
public void run() throws Exception {
|
||||
|
||||
@ -39,9 +53,6 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||
askProjectFolder("Please choose a folder for your Version Tracking session.");
|
||||
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
||||
|
||||
Program sourceProgram;
|
||||
Program destinationProgram;
|
||||
|
||||
boolean isCurrentProgramSourceProg = askYesNo("Current Program 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");
|
||||
}
|
||||
|
||||
if (sourceProgram == null || destinationProgram == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to end the script transaction or it interferes with vt things that need locks
|
||||
end(true);
|
||||
|
||||
@ -81,9 +96,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
||||
|
||||
public static <T extends Plugin> T getPlugin(PluginTool tool, Class<T> c) {
|
||||
List<Plugin> list = tool.getManagedPlugins();
|
||||
Iterator<Plugin> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
Plugin p = it.next();
|
||||
for (Plugin p : list) {
|
||||
if (p.getClass() == c) {
|
||||
return c.cast(p);
|
||||
}
|
||||
|
@ -17,6 +17,9 @@
|
||||
// data and and then save the session.
|
||||
//@category Examples.Version Tracking
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.feature.vt.api.correlator.program.*;
|
||||
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.util.exception.CancelledException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
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
|
||||
public void run() throws Exception {
|
||||
DomainFolder folder =
|
||||
askProjectFolder("Please choose a folder for the session domain object");
|
||||
String name = askString("Please enter a Version Tracking session name", "Session Name");
|
||||
Program sourceProgram = askProgram("Please select the source (existing annotated) program");
|
||||
Program destinationProgram = askProgram("Please select the destination (new) program");
|
||||
sourceProgram = askProgram("Please select the source (existing annotated) program");
|
||||
if (sourceProgram == null) {
|
||||
return;
|
||||
}
|
||||
destinationProgram = askProgram("Please select the destination (new) program");
|
||||
if (destinationProgram == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VTSession session =
|
||||
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
|
||||
// take address sets, thus implying the entire address space should be used?
|
||||
AddressSetView sourceAddressSet = sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||
AddressSetView sourceAddressSet =
|
||||
sourceProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||
AddressSetView destinationAddressSet =
|
||||
destinationProgram.getMemory().getLoadedAndInitializedAddressSet();
|
||||
|
||||
@ -91,17 +113,16 @@ public class CreateAppliedExactMatchingSessionScript extends GhidraScript {
|
||||
private void correlateAndPossiblyApply(Program sourceProgram, Program destinationProgram,
|
||||
VTSession session, PluginTool serviceProvider, VTAssociationManager manager,
|
||||
AddressSetView sourceAddressSet, AddressSetView destinationAddressSet,
|
||||
VTProgramCorrelatorFactory factory) throws CancelledException,
|
||||
VTAssociationStatusException {
|
||||
VTProgramCorrelatorFactory factory)
|
||||
throws CancelledException, VTAssociationStatusException {
|
||||
|
||||
AddressSetView restrictedSourceAddresses =
|
||||
excludeAcceptedMatches(session, sourceAddressSet, true);
|
||||
AddressSetView restrictedDestinationAddresses =
|
||||
excludeAcceptedMatches(session, destinationAddressSet, false);
|
||||
VTOptions options = factory.createDefaultOptions();
|
||||
VTProgramCorrelator correlator =
|
||||
factory.createCorrelator(serviceProvider, sourceProgram, restrictedSourceAddresses,
|
||||
destinationProgram, restrictedDestinationAddresses, options);
|
||||
VTProgramCorrelator correlator = factory.createCorrelator(serviceProvider, sourceProgram,
|
||||
restrictedSourceAddresses, destinationProgram, restrictedDestinationAddresses, options);
|
||||
|
||||
VTMatchSet results = correlator.correlate(session, monitor);
|
||||
applyMatches(manager, results.getMatches());
|
||||
|
@ -29,26 +29,47 @@ import ghidra.util.Msg;
|
||||
|
||||
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
|
||||
protected void run() throws Exception {
|
||||
Project project = state.getProject();
|
||||
if (project == null) {
|
||||
throw new RuntimeException("No project open");
|
||||
}
|
||||
|
||||
|
||||
// Prompt the user to load the two programs that will be analyzed.
|
||||
// This will only allow you to select programs from the currently-open
|
||||
// project in Ghidra, so import them if you haven't already.
|
||||
Program p1 = askProgram("Program1_Version1");
|
||||
Program p2 = askProgram("Program1_Version2");
|
||||
|
||||
p1 = askProgram("Program1_Version1");
|
||||
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,
|
||||
// warn the user.
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Create a new VT session
|
||||
createVersionTrackingSession("new session", p1, p2);
|
||||
|
||||
@ -67,7 +88,7 @@ public class FindChangedFunctionsScript extends GhidraVersionTrackingScript {
|
||||
println("Did not find exact match for: " + functionName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if one of the programs is locked.
|
||||
* <p>
|
||||
|
@ -82,7 +82,7 @@ public class AutoVersionTrackingTask extends Task {
|
||||
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
|
||||
* tool information needed for this command.
|
||||
@ -483,8 +483,6 @@ public class AutoVersionTrackingTask extends Task {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
Set<Address> sourceAddresses = getSourceAddressesFromMatches(relatedMatches, monitor);
|
||||
|
@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||
return;
|
||||
}
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||
BufferFile bf = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
|
||||
throws IOException {
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
|
||||
FolderItem item = userFilesystem.getItem(path, name);
|
||||
if (item != null) {
|
||||
item.delete(-1, null);
|
||||
@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
||||
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||
FolderItem item = userfs.getItem(path, name);
|
||||
if (item == null || !(item instanceof DatabaseItem) ||
|
||||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {
|
||||
|
@ -16,12 +16,14 @@
|
||||
package ghidra.framework.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.timer.GhidraSwinglessTimer;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
|
||||
/**
|
||||
* 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 INDEXED_DATA_FOLDER_NAME = "idata";
|
||||
public static final String USER_FOLDER_NAME = "user";
|
||||
@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
|
||||
|
||||
private RootGhidraFolderData rootFolderData;
|
||||
|
||||
private Map<String, DomainObjectAdapter> openDomainObjects =
|
||||
new HashMap<>();
|
||||
private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
|
||||
|
||||
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
|
||||
|
||||
private ProjectLock projectLock;
|
||||
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.
|
||||
* @param localStorageLocator the location of the project
|
||||
@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
* write lock (i.e., project in-use)
|
||||
* @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 {
|
||||
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
|
||||
* lock (i.e., project in-use)
|
||||
*/
|
||||
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||
boolean isInWritableProject) throws IOException, LockException {
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
this.repository = repository;
|
||||
@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
* @param versionedFileSystem an existing versioned file-system
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
||||
DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
||||
throws IOException {
|
||||
this.localStorageLocator = new ProjectLocator(null, "Test");
|
||||
owner = SystemUtilities.getUserName();
|
||||
@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return the owner of the project
|
||||
*/
|
||||
@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData {
|
||||
|
||||
@Override
|
||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
||||
newRepository.connect();
|
||||
if (!newRepository.isConnected()) {
|
||||
throw new IOException("new respository not connected");
|
||||
@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
|
||||
long checkoutId = item.getCheckoutId();
|
||||
int checkoutVersion = item.getCheckoutVersion();
|
||||
|
||||
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
|
||||
df.getParent().getPathname(), df.getName(), checkoutId);
|
||||
ItemCheckoutStatus otherCheckoutStatus =
|
||||
newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
|
||||
|
||||
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
|
||||
return true;
|
||||
@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
@Override
|
||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
@Override
|
||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
}
|
||||
|
||||
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
||||
for (String name : fileSystem.getItemNames(folderPath)) {
|
||||
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.
|
||||
* @param associatedFileID the file id
|
||||
@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
}
|
||||
|
||||
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
|
||||
synchronized (ProjectFileManager.this) {
|
||||
synchronized (DefaultProjectData.this) {
|
||||
startReconcileUserDataFiles();
|
||||
}
|
||||
});
|
||||
@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
|
||||
return projectDir;
|
||||
}
|
||||
|
||||
public synchronized boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public synchronized boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (this) {
|
||||
if (!closed) {
|
||||
Msg.debug(this, "Closing ProjectData: " + projectDir);
|
||||
closed = true;
|
||||
}
|
||||
if (inUseCount != 0) {
|
||||
return; // delay 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) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Msg.debug(this, "Disposing ProjectData: " + projectDir);
|
||||
|
||||
closed = true;
|
||||
disposed = true;
|
||||
|
||||
if (userDataReconcileTimer != null) {
|
||||
userDataReconcileTimer.stop();
|
||||
}
|
||||
@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
|
||||
/**
|
||||
* Returns the open domain object (opened for update) for the specified path.
|
||||
* @param pathname the path name
|
||||
* @return the domain object
|
||||
* @return the domain object or null if not open
|
||||
*/
|
||||
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
|
||||
return openDomainObjects.get(pathname);
|
||||
@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
|
||||
public TaskMonitor getProjectDisposalMonitor() {
|
||||
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
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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 {
|
||||
|
||||
private ProjectFileManager projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
|
||||
|
||||
DomainFileIndex(ProjectFileManager projectData) {
|
||||
DomainFileIndex(DefaultProjectData projectData) {
|
||||
this.projectData = projectData;
|
||||
}
|
||||
|
||||
|
@ -23,14 +23,11 @@ import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.RepositoryItem;
|
||||
import ghidra.framework.store.ItemCheckoutStatus;
|
||||
import ghidra.framework.store.Version;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.db.PackedDatabase;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
|
||||
|
||||
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
|
||||
try {
|
||||
String spec = getPathname().substring(1); // remove leading '/'
|
||||
if (!StringUtils.isEmpty(ref)) {
|
||||
spec += "#" + ref;
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
String urlStr = sharedProjectURL.toExternalForm();
|
||||
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) {
|
||||
// ignore
|
||||
@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
|
||||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME);
|
||||
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME);
|
||||
String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
|
||||
String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
|
||||
if (serverName == null || repoName == 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)) {
|
||||
return null; // avoid initiating a server connection.
|
||||
@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
|
||||
return getSharedFileURL(projectURL, ref);
|
||||
}
|
||||
Properties properties =
|
||||
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
|
||||
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
|
||||
return getSharedFileURL(properties, ref);
|
||||
}
|
||||
return null;
|
||||
|
@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
||||
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
|
||||
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
|
||||
private volatile boolean eventsEnabled = true;
|
||||
private Set<DomainObjectClosedListener> closeListeners =
|
||||
new CopyOnWriteArraySet<DomainObjectClosedListener>();
|
||||
private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private ArrayList<Object> consumers;
|
||||
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
|
||||
@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
||||
|
||||
private void notifyCloseListeners() {
|
||||
for (DomainObjectClosedListener listener : closeListeners) {
|
||||
listener.domainObjectClosed();
|
||||
listener.domainObjectClosed(this);
|
||||
}
|
||||
closeListeners.clear();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
|
||||
|
||||
protected ProjectFileManager fileManager;
|
||||
protected DefaultProjectData projectData;
|
||||
|
||||
private LocalFileSystem fileSystem;
|
||||
private DomainFolderChangeListener listener;
|
||||
@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.listener = parent.getChangeListener();
|
||||
}
|
||||
|
||||
public LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
|
||||
@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
void clearDomainObj() {
|
||||
String path = getPathname();
|
||||
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
|
||||
if (doa != null && fileManager.clearDomainObject(getPathname())) {
|
||||
DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
|
||||
if (doa != null && projectData.clearDomainObject(getPathname())) {
|
||||
listener.domainFileObjectClosed(this, doa);
|
||||
}
|
||||
}
|
||||
@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public ProjectLocator getProjectLocator() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public DomainObject getOpenedDomainObject(Object consumer) {
|
||||
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (domainObj != null) {
|
||||
if (!domainObj.addConsumer(consumer)) {
|
||||
fileManager.clearDomainObject(getPathname());
|
||||
projectData.clearDomainObject(getPathname());
|
||||
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
|
||||
}
|
||||
}
|
||||
@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public void save(TaskMonitor monitor) throws IOException, CancelledException {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
throw new AssertException("Cannot save, domainObj not open");
|
||||
}
|
||||
@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public boolean canSave() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
return false;
|
||||
}
|
||||
@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public ArrayList<?> getConsumers() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public boolean isChanged() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
return dobj != null && dobj.isChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return fileManager.getOpenedDomainObject(getPathname()) != null;
|
||||
return projectData.getOpenedDomainObject(getPathname()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
|
||||
return false;
|
||||
}
|
||||
GhidraFile other = (GhidraFile) obj;
|
||||
if (fileManager != other.fileManager) {
|
||||
if (projectData != other.projectData) {
|
||||
return false;
|
||||
}
|
||||
return getPathname().equals(other.getPathname());
|
||||
@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
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.util.List;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GhidraFolder implements DomainFolder {
|
||||
|
||||
private ProjectFileManager fileManager;
|
||||
private DefaultProjectData projectData;
|
||||
private LocalFileSystem fileSystem;
|
||||
private FileSystem versionedFileSystem;
|
||||
private DomainFolderChangeListener listener;
|
||||
@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
|
||||
private GhidraFolder parent;
|
||||
private String name;
|
||||
|
||||
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
this.fileManager = fileManager;
|
||||
this.fileSystem = fileManager.getLocalFileSystem();
|
||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
||||
GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
this.projectData = projectData;
|
||||
this.fileSystem = projectData.getLocalFileSystem();
|
||||
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||
this.listener = listener;
|
||||
this.name = FileSystem.SEPARATOR;
|
||||
}
|
||||
@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||
this.listener = parent.getChangeListener();
|
||||
@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
|
||||
}
|
||||
|
||||
LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
DomainFolderChangeListener getChangeListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
ProjectFileManager getProjectFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
|
||||
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
|
||||
if (fileData == null) {
|
||||
@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
GhidraFolderData getFolderData() throws FileNotFoundException {
|
||||
if (parent == null) {
|
||||
return fileManager.getRootFolderData();
|
||||
return projectData.getRootFolderData();
|
||||
}
|
||||
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
|
||||
if (folderData == null) {
|
||||
@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
private GhidraFolderData createFolderData(String folderName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
GhidraFolderData parentData =
|
||||
parent == null ? fileManager.getRootFolderData() : createFolderData();
|
||||
parent == null ? projectData.getRootFolderData() : createFolderData();
|
||||
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
|
||||
if (folderData == null) {
|
||||
try {
|
||||
@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
}
|
||||
|
||||
private GhidraFolderData createFolderData() throws IOException {
|
||||
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
|
||||
GhidraFolderData rootFolderData = projectData.getRootFolderData();
|
||||
if (parent == null) {
|
||||
return rootFolderData;
|
||||
}
|
||||
@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
@Override
|
||||
public ProjectLocator getProjectLocator() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectFileManager getProjectData() {
|
||||
return fileManager;
|
||||
public DefaultProjectData getProjectData() {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
String getPathname(String childName) {
|
||||
@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
ProjectLocator projectLocator = getProjectLocator();
|
||||
URL projectURL = projectLocator.getURL();
|
||||
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||
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());
|
||||
URL projectURL = projectData.getSharedProjectURL();
|
||||
if (projectURL == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
ProjectLocator projectLocator = parent.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
if (!projectLocator.isTransient()) {
|
||||
return GhidraURL.makeURL(projectLocator, getPathname(), null);
|
||||
}
|
||||
@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
@Override
|
||||
public boolean isInWritableProject() {
|
||||
return !getProjectData().getLocalFileSystem().isReadOnly();
|
||||
return !fileSystem.isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
|
||||
Msg.error(this, "file error for " + getPathname(fileName), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
|
||||
return false;
|
||||
}
|
||||
GhidraFolder other = (GhidraFolder) obj;
|
||||
if (fileManager != other.fileManager) {
|
||||
if (projectData != other.projectData) {
|
||||
return false;
|
||||
}
|
||||
return getPathname().equals(other.getPathname());
|
||||
@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
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.TransientProjectData;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.FolderNotEmptyException;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.*;
|
||||
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 {
|
||||
|
||||
private ProjectFileManager fileManager;
|
||||
private DefaultProjectData projectData;
|
||||
|
||||
/**
|
||||
* Folder change listener - change events only sent if folder is visited
|
||||
@ -59,17 +70,24 @@ class GhidraFolderData {
|
||||
private boolean versionedFolderExists;
|
||||
|
||||
/**
|
||||
* General constructor reserved for root folder use only
|
||||
* @param fileManager
|
||||
* @param listener
|
||||
* General constructor reserved for root folder instantiation
|
||||
* @param projectData associated project data instance
|
||||
* @param listener folder change listener
|
||||
*/
|
||||
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
this.fileManager = fileManager;
|
||||
this.fileSystem = fileManager.getLocalFileSystem();
|
||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
||||
GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
this.projectData = projectData;
|
||||
this.fileSystem = projectData.getLocalFileSystem();
|
||||
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||
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 {
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new FileNotFoundException("Bad folder name: blank or null");
|
||||
@ -77,7 +95,7 @@ class GhidraFolderData {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||
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() {
|
||||
return visited;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local file system
|
||||
*/
|
||||
LocalFileSystem getLocalFileSystem() {
|
||||
return fileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return versioned file system
|
||||
*/
|
||||
FileSystem getVersionedFileSystem() {
|
||||
return versionedFileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local user data file system
|
||||
*/
|
||||
LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return folder change listener
|
||||
*/
|
||||
DomainFolderChangeListener getChangeListener() {
|
||||
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() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this folder's parent folder or null if this is the root folder.
|
||||
*/
|
||||
GhidraFolderData getParentData() {
|
||||
return parent;
|
||||
}
|
||||
@ -143,7 +184,7 @@ class GhidraFolderData {
|
||||
}
|
||||
}
|
||||
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
|
||||
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy);
|
||||
return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
|
||||
}
|
||||
if (folderPath.length() == 0) {
|
||||
return this;
|
||||
@ -168,10 +209,26 @@ class GhidraFolderData {
|
||||
return folderData.getFolderPathData(nextPath, lazy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this folder's name.
|
||||
* @return the name
|
||||
*/
|
||||
String getName() {
|
||||
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 {
|
||||
synchronized (fileSystem) {
|
||||
if (parent == null || fileSystem.isReadOnly()) {
|
||||
@ -242,6 +299,10 @@ class GhidraFolderData {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path name to this folder
|
||||
* @return the path name
|
||||
*/
|
||||
String getPathname() {
|
||||
if (parent == null) {
|
||||
return FileSystem.SEPARATOR;
|
||||
@ -254,6 +315,10 @@ class GhidraFolderData {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this folder contains any sub-folders or domain files.
|
||||
* @return true if this folder is empty.
|
||||
*/
|
||||
boolean isEmpty() {
|
||||
try {
|
||||
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() {
|
||||
try {
|
||||
refresh(false, false, null); // visited will be true upon return
|
||||
@ -277,6 +346,10 @@ class GhidraFolderData {
|
||||
return new ArrayList<>(fileList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of names for all subfolders contained within this folder.
|
||||
* @return list of file names
|
||||
*/
|
||||
List<String> getFolderNames() {
|
||||
try {
|
||||
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.
|
||||
* If this folder has been visited listener will be notified with rename
|
||||
* @param oldName
|
||||
* Update file list/cache based upon rename of a file.
|
||||
* If this folder has been visited the listener will be notified with rename
|
||||
* @param oldFileName file name prior to rename
|
||||
* @param newFileName file name after rename
|
||||
*/
|
||||
void fileRenamed(String oldFileName, String newFileName) {
|
||||
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) {
|
||||
GhidraFileData fileData;
|
||||
synchronized (fileSystem) {
|
||||
@ -340,7 +422,7 @@ class GhidraFolderData {
|
||||
* underlying local or versioned file. If this folder has been visited an appropriate
|
||||
* add/remove/change notification will be provided to the listener.
|
||||
* NOTE: Move and Rename situations are not handled
|
||||
* @param fileName
|
||||
* @param fileName name of file which has changed
|
||||
*/
|
||||
void fileChanged(String fileName) {
|
||||
synchronized (fileSystem) {
|
||||
@ -389,7 +471,8 @@ class GhidraFolderData {
|
||||
* 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: 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 {
|
||||
synchronized (fileSystem) {
|
||||
@ -404,7 +487,7 @@ class GhidraFolderData {
|
||||
if (folderData.versionedFolderExists || folderData.folderExists) {
|
||||
// preserve subfolder data
|
||||
if (folderData.visited) {
|
||||
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor());
|
||||
folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -429,7 +512,7 @@ class GhidraFolderData {
|
||||
/**
|
||||
* Remove and dispose specified subfolder data and notify listener of removal
|
||||
* if this folder has been visited
|
||||
* @param folderName
|
||||
* @param folderName name of folder which was removed
|
||||
*/
|
||||
void folderRemoved(String folderName) {
|
||||
synchronized (fileSystem) {
|
||||
@ -443,6 +526,9 @@ class GhidraFolderData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the cached data for this folder and all of its children recursively.
|
||||
*/
|
||||
void dispose() {
|
||||
visited = false;
|
||||
folderList.clear();
|
||||
@ -458,7 +544,7 @@ class GhidraFolderData {
|
||||
// NOTE: clearing the following can cause issues since there may be some residual
|
||||
// activity/use which will get a NPE
|
||||
// parent = null;
|
||||
// fileManager = null;
|
||||
// projectData = null;
|
||||
// listener = null;
|
||||
}
|
||||
|
||||
@ -476,7 +562,7 @@ class GhidraFolderData {
|
||||
* Refresh set of sub-folder names and identify added/removed folders.
|
||||
* @param recursive recurse into visited subfolders if true
|
||||
* @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 {
|
||||
|
||||
@ -653,7 +739,7 @@ class GhidraFolderData {
|
||||
* of visited state, if false refresh is lazy and will not be
|
||||
* performed if a previous refresh set the visited state.
|
||||
* @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 {
|
||||
synchronized (fileSystem) {
|
||||
@ -699,11 +785,11 @@ class GhidraFolderData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for existence of subfolder. If this folder visited, rely on folderList
|
||||
* @param fileName
|
||||
* @param doRealCheck if true do not rely on fileList
|
||||
* @return
|
||||
* @throws IOException
|
||||
* Check for existence of subfolder. If this folder has previously been visited,
|
||||
* rely on the cached folderList.
|
||||
* @param folderName name of folder to look for
|
||||
* @return true if folder exists, else false
|
||||
* @throws IOException if an IO error occurs when checking for folder's existance.
|
||||
*/
|
||||
boolean containsFolder(String folderName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
@ -720,8 +806,8 @@ class GhidraFolderData {
|
||||
/**
|
||||
* Create and add new subfolder data object to cache. Data will not be created
|
||||
* if folder does not exist or an IOException occurs.
|
||||
* @param folderName
|
||||
* @return folder data or null
|
||||
* @param folderName name of folder to be added
|
||||
* @return folder data or null if folder does not exist
|
||||
*/
|
||||
private GhidraFolderData addFolderData(String folderName) {
|
||||
GhidraFolderData folderData = folderDataCache.get(folderName);
|
||||
@ -739,7 +825,7 @@ class GhidraFolderData {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* this case null will be returned
|
||||
* @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
|
||||
* @param fileName the name of the file to check for
|
||||
* @return true if this folder contains the fileName
|
||||
* @throws IOException
|
||||
* Check for existence of file. If folder previously visited, rely on fileDataCache
|
||||
* @param fileName the name of the file to look for
|
||||
* @return true if this folder contains the fileName, else false
|
||||
* @throws IOException if an IO error occurs while checking for file existance
|
||||
*/
|
||||
public boolean containsFile(String fileName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
@ -783,9 +869,9 @@ class GhidraFolderData {
|
||||
/**
|
||||
* Create and add new file data object to cache. Data will not be created
|
||||
* if file does not exist or an IOException occurs.
|
||||
* @param fileName
|
||||
* @return file data or null
|
||||
* @throws IOException
|
||||
* @param fileName name of file
|
||||
* @return file data or null if not found
|
||||
* @throws IOException if an IO error occurs while checking for file existance
|
||||
*/
|
||||
private GhidraFileData addFileData(String fileName) throws IOException {
|
||||
GhidraFileData fileData = fileDataCache.get(fileName);
|
||||
@ -793,7 +879,7 @@ class GhidraFolderData {
|
||||
try {
|
||||
fileData = new GhidraFileData(this, fileName);
|
||||
fileDataCache.put(fileName, fileData);
|
||||
fileManager.updateFileIndex(fileData);
|
||||
projectData.updateFileIndex(fileData);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
@ -804,10 +890,11 @@ class GhidraFolderData {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* this case null will be returned
|
||||
* @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 {
|
||||
synchronized (fileSystem) {
|
||||
@ -822,58 +909,11 @@ class GhidraFolderData {
|
||||
return null;
|
||||
}
|
||||
|
||||
// // TODO: Examine!
|
||||
// private void removeFolderX(String folderName) {
|
||||
// folderList.remove(folderName);
|
||||
// folderDataCache.remove(folderName);
|
||||
// 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;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get the domain file in this folder with the given fileName.
|
||||
* @param fileName name of file in this folder to retrieve
|
||||
* @return domain file or null if there is no file in this folder with the given name.
|
||||
*/
|
||||
GhidraFile getDomainFile(String fileName) {
|
||||
synchronized (fileSystem) {
|
||||
try {
|
||||
@ -888,6 +928,11 @@ class GhidraFolderData {
|
||||
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) {
|
||||
synchronized (fileSystem) {
|
||||
try {
|
||||
@ -902,10 +947,26 @@ class GhidraFolderData {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link DomainFolder} instance which corresponds to this folder
|
||||
*/
|
||||
GhidraFolder getDomainFolder() {
|
||||
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)
|
||||
throws InvalidNameException, IOException, CancelledException {
|
||||
synchronized (fileSystem) {
|
||||
@ -937,9 +998,12 @@ class GhidraFolderData {
|
||||
throw new IOException("File creation failed for unknown reason");
|
||||
}
|
||||
|
||||
fileManager.setDomainObject(file.getPathname(), doa);
|
||||
projectData.setDomainObject(file.getPathname(), doa);
|
||||
doa.setDomainFile(file);
|
||||
doa.setChanged(false);
|
||||
|
||||
projectData.trackDomainFileInUse(doa);
|
||||
|
||||
listener.domainFileObjectOpenedForUpdate(file, doa);
|
||||
|
||||
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)
|
||||
throws InvalidNameException, IOException, CancelledException {
|
||||
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 {
|
||||
synchronized (fileSystem) {
|
||||
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 {
|
||||
synchronized (fileSystem) {
|
||||
if (fileSystem.isReadOnly()) {
|
||||
@ -1000,6 +1091,9 @@ class GhidraFolderData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this folder from the local filesystem if empty
|
||||
*/
|
||||
void deleteLocalFolderIfEmpty() {
|
||||
synchronized (fileSystem) {
|
||||
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 {
|
||||
synchronized (fileSystem) {
|
||||
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) {
|
||||
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) {
|
||||
if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
|
||||
// check if projects share a common repository
|
||||
RepositoryAdapter myRepository = fileManager.getRepository();
|
||||
RepositoryAdapter otherRepository = folderData.fileManager.getRepository();
|
||||
RepositoryAdapter myRepository = projectData.getRepository();
|
||||
RepositoryAdapter otherRepository = folderData.projectData.getRepository();
|
||||
if (myRepository == null || otherRepository == null ||
|
||||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
|
||||
!myRepository.getName().equals(otherRepository.getName())) {
|
||||
@ -1105,20 +1218,30 @@ class GhidraFolderData {
|
||||
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 {
|
||||
synchronized (fileSystem) {
|
||||
if (newParentData.fileSystem.isReadOnly()) {
|
||||
if (newParent.fileSystem.isReadOnly()) {
|
||||
throw new ReadOnlyException("copyTo permitted to writeable project only");
|
||||
}
|
||||
if (isAncestor(newParentData)) {
|
||||
if (isAncestor(newParent)) {
|
||||
throw new IOException("self-referencing copy not permitted");
|
||||
}
|
||||
GhidraFolderData newFolderData = newParentData.getFolderData(name, false);
|
||||
GhidraFolderData newFolderData = newParent.getFolderData(name, false);
|
||||
|
||||
if (newFolderData == null) {
|
||||
try {
|
||||
newFolderData = newParentData.createFolder(name);
|
||||
newFolderData = newParent.createFolder(name);
|
||||
}
|
||||
catch (InvalidNameException 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) {
|
||||
String linkFilename = name;
|
||||
if (linkFilename == null) {
|
||||
if (fileManager instanceof TransientProjectData) {
|
||||
linkFilename = fileManager.getRepository().getName();
|
||||
if (projectData instanceof TransientProjectData) {
|
||||
linkFilename = projectData.getRepository().getName();
|
||||
}
|
||||
else {
|
||||
linkFilename = fileManager.getProjectLocator().getName();
|
||||
linkFilename = projectData.getProjectLocator().getName();
|
||||
}
|
||||
}
|
||||
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename,
|
||||
return newParent.copyAsLink(projectData, getPathname(), linkFilename,
|
||||
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,
|
||||
LinkHandler<?> lh) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
@ -1167,7 +1314,7 @@ class GhidraFolderData {
|
||||
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
|
||||
}
|
||||
|
||||
if (sourceProjectData == fileManager) {
|
||||
if (sourceProjectData == projectData) {
|
||||
// internal linking not yet supported
|
||||
Msg.error(this, "Internal file/folder links not yet supported");
|
||||
return null;
|
||||
@ -1177,13 +1324,12 @@ class GhidraFolderData {
|
||||
if (sourceProjectData instanceof TransientProjectData) {
|
||||
RepositoryAdapter repository = sourceProjectData.getRepository();
|
||||
ServerInfo serverInfo = repository.getServerInfo();
|
||||
ghidraUrl =
|
||||
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
||||
repository.getName(), pathname);
|
||||
ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
|
||||
serverInfo.getPortNumber(), repository.getName(), pathname);
|
||||
}
|
||||
else {
|
||||
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
|
||||
if (projectLocator.equals(fileManager.getProjectLocator())) {
|
||||
if (projectLocator.equals(projectData.getProjectLocator())) {
|
||||
return null; // local internal linking not supported
|
||||
}
|
||||
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 newName = preferredName;
|
||||
int i = 1;
|
||||
@ -1242,11 +1396,11 @@ class GhidraFolderData {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
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
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (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 {
|
||||
|
||||
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
super(fileManager, listener);
|
||||
RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
super(projectData, listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem;
|
||||
|
||||
public class RootGhidraFolderData extends GhidraFolderData {
|
||||
|
||||
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
super(fileManager, listener);
|
||||
RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
super(projectData, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
GhidraFolder getDomainFolder() {
|
||||
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
|
||||
return new RootGhidraFolder(getProjectData(), getChangeListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided for testing use only
|
||||
* @param fs
|
||||
* @param fs versioned file system
|
||||
*/
|
||||
void setVersionedFileSystem(FileSystem fs) {
|
||||
versionedFileSystem = fs;
|
||||
|
@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||
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
|
||||
*/
|
||||
public String getName();
|
||||
@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||
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
|
||||
*/
|
||||
public String getPathname();
|
||||
@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||
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}
|
||||
* 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.
|
||||
* NOTE: This method is unable to cope with version issues which may require an
|
||||
* upgrade.
|
||||
* @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
|
||||
* 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
|
||||
* extension.
|
||||
* @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.
|
||||
*/
|
||||
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.
|
||||
* @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();
|
||||
|
||||
@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
||||
/**
|
||||
* 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 to tracks changes, etc.
|
||||
* used to track changes, etc.
|
||||
* @return file length
|
||||
* @throws IOException if IO or access error occurs
|
||||
*/
|
||||
|
@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||
public ProjectData getProjectData();
|
||||
|
||||
/**
|
||||
* Returns the path name to the domain object.
|
||||
* Returns the full path name to this folder
|
||||
* @return the path name
|
||||
*/
|
||||
public String getPathname();
|
||||
@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||
throws InvalidNameException, IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Create a subfolder of this folder.
|
||||
* Create a subfolder within this folder.
|
||||
* @param folderName sub-folder name
|
||||
* @return the folder
|
||||
* @throws DuplicateFileException if a folder by
|
||||
* this name already exists
|
||||
* @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
|
||||
@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||
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 FolderNotEmptyException Thrown if the subfolder is not empty.
|
||||
* @throws FolderNotEmptyException Thrown if this folder is not empty.
|
||||
*/
|
||||
public void delete() throws IOException;
|
||||
|
||||
/**
|
||||
* Move this folder into the newParent folder. If connected to an archive
|
||||
* this affects both private and repository folders and files. If not
|
||||
* connected, only private folders and files are affected.
|
||||
* 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)
|
||||
@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||
* Copy this folder into the newParent folder.
|
||||
* @param newParent new parent folder
|
||||
* @param monitor the task monitor
|
||||
* @return the copied folder
|
||||
* @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.
|
||||
@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
||||
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>
|
||||
* <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 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.
|
||||
* @param newParent new parent folder
|
||||
* @return newly created domain file or null if link use not supported.
|
||||
* @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.
|
||||
*/
|
||||
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,5 +19,10 @@ package ghidra.framework.model;
|
||||
* An interface that allows for a callback when a {@link DomainObject} is closed.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Close the project storage associated with this project data object.
|
||||
* NOTE: This should not be invoked if this object is utilized by a Project instance.
|
||||
* Initiate disposal of this project data object. Any files already open will delay
|
||||
* 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();
|
||||
|
||||
@ -209,4 +211,18 @@ public interface ProjectData {
|
||||
*/
|
||||
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 ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.data.TransientDataManager;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
@ -58,7 +58,7 @@ public class DefaultProject implements Project {
|
||||
private DefaultProjectManager projectManager;
|
||||
|
||||
private ProjectLocator projectLocator;
|
||||
private ProjectFileManager fileMgr;
|
||||
private DefaultProjectData projectData;
|
||||
private ToolManagerImpl toolManager;
|
||||
|
||||
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, 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 WeakSet<ProjectViewListener> viewListeners =
|
||||
WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
@ -86,21 +86,10 @@ public class DefaultProject implements Project {
|
||||
this.projectManager = projectManager;
|
||||
this.projectLocator = projectLocator;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Creating project: " + projectLocator.toString());
|
||||
fileMgr = new ProjectFileManager(projectLocator, repository, true);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Creating project: " + projectLocator.toString());
|
||||
projectData = new DefaultProjectData(projectLocator, repository, true);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
initializeNewProject();
|
||||
}
|
||||
@ -122,28 +111,18 @@ public class DefaultProject implements Project {
|
||||
this.projectManager = projectManager;
|
||||
this.projectLocator = projectLocator;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Opening project: " + projectLocator.toString());
|
||||
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Opening project: " + projectLocator.toString());
|
||||
projectData = new DefaultProjectData(projectLocator, true, resetOwner);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
|
||||
@ -151,27 +130,17 @@ public class DefaultProject implements Project {
|
||||
|
||||
this.projectManager = projectManager;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
||||
fileMgr = (ProjectFileManager) connection.getProjectData();
|
||||
if (fileMgr == null) {
|
||||
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
||||
}
|
||||
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
||||
projectData = (DefaultProjectData) connection.getProjectData();
|
||||
if (projectData == null) {
|
||||
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
||||
}
|
||||
|
||||
projectLocator = fileMgr.getProjectLocator();
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
projectLocator = projectData.getProjectLocator();
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
|
||||
initializeNewProject();
|
||||
}
|
||||
|
||||
@ -300,20 +269,20 @@ public class DefaultProject implements Project {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData();
|
||||
DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
|
||||
if (projectData == null) {
|
||||
throw new IOException(
|
||||
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
|
||||
}
|
||||
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
|
||||
|
||||
otherViews.put(url, projectData);
|
||||
otherViewsMap.put(url, projectData);
|
||||
return projectData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
if (isClosed) {
|
||||
throw new IOException("project is closed");
|
||||
}
|
||||
@ -322,7 +291,7 @@ public class DefaultProject implements Project {
|
||||
throw new IOException("Invalid Ghidra URL specified: " + url);
|
||||
}
|
||||
|
||||
ProjectData projectData = otherViews.get(url);
|
||||
ProjectData projectData = otherViewsMap.get(url);
|
||||
if (projectData == null) {
|
||||
projectData = openProjectView(url);
|
||||
}
|
||||
@ -340,13 +309,13 @@ public class DefaultProject implements Project {
|
||||
*/
|
||||
@Override
|
||||
public void removeProjectView(URL url) {
|
||||
synchronized (otherViews) {
|
||||
ProjectFileManager dataMgr = otherViews.remove(url);
|
||||
synchronized (otherViewsMap) {
|
||||
DefaultProjectData dataMgr = otherViewsMap.remove(url);
|
||||
if (dataMgr != null) {
|
||||
if (visibleViews.remove(url)) {
|
||||
notifyVisibleViewRemoved(url);
|
||||
}
|
||||
dataMgr.dispose();
|
||||
dataMgr.close();
|
||||
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
|
||||
changed = true;
|
||||
}
|
||||
@ -401,22 +370,20 @@ public class DefaultProject implements Project {
|
||||
|
||||
@Override
|
||||
public RepositoryAdapter getRepository() {
|
||||
return fileMgr.getRepository();
|
||||
return projectData.getRepository();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
isClosed = true;
|
||||
|
||||
Iterator<ProjectFileManager> iter = otherViews.values().iterator();
|
||||
while (iter.hasNext()) {
|
||||
ProjectFileManager dataMgr = iter.next();
|
||||
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
|
||||
if (dataMgr != null) {
|
||||
dataMgr.dispose();
|
||||
dataMgr.close();
|
||||
}
|
||||
}
|
||||
otherViews.clear();
|
||||
otherViewsMap.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
@ -429,7 +396,7 @@ public class DefaultProject implements Project {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
fileMgr.dispose();
|
||||
projectData.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +416,7 @@ public class DefaultProject implements Project {
|
||||
@Override
|
||||
public void restore() {
|
||||
// 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;
|
||||
Throwable error = null;
|
||||
try {
|
||||
@ -593,7 +560,7 @@ public class DefaultProject implements Project {
|
||||
try {
|
||||
// save tool 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);
|
||||
Document doc = new Document(root);
|
||||
XMLOutputter xmlOut = new GenericXMLOutputter();
|
||||
@ -629,15 +596,14 @@ public class DefaultProject implements Project {
|
||||
@Override
|
||||
public List<DomainFile> getOpenData() {
|
||||
ArrayList<DomainFile> openFiles = new ArrayList<>();
|
||||
fileMgr.findOpenFiles(openFiles);
|
||||
projectData.findOpenFiles(openFiles);
|
||||
ProjectData[] viewedProjs = getViewedProjectData();
|
||||
for (ProjectData viewedProj : viewedProjs) {
|
||||
((ProjectFileManager) viewedProj).findOpenFiles(openFiles);
|
||||
((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
|
||||
}
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
TransientDataManager.getTransients(list);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
DomainFile df = list.get(i);
|
||||
for (DomainFile df : list) {
|
||||
if (df != null && df.isOpen()) {
|
||||
openFiles.add(df);
|
||||
}
|
||||
@ -646,8 +612,8 @@ public class DefaultProject implements Project {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectFileManager getProjectData() {
|
||||
return fileMgr;
|
||||
public DefaultProjectData getProjectData() {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -662,12 +628,12 @@ public class DefaultProject implements Project {
|
||||
|
||||
@Override
|
||||
public ProjectData getProjectData(ProjectLocator locator) {
|
||||
if (locator.equals(fileMgr.getProjectLocator())) {
|
||||
return fileMgr;
|
||||
if (locator.equals(projectData.getProjectLocator())) {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
synchronized (otherViews) {
|
||||
for (ProjectData data : otherViews.values()) {
|
||||
synchronized (otherViewsMap) {
|
||||
for (ProjectData data : otherViewsMap.values()) {
|
||||
if (locator.equals(data.getProjectLocator())) {
|
||||
return data;
|
||||
}
|
||||
@ -680,30 +646,30 @@ public class DefaultProject implements Project {
|
||||
@Override
|
||||
public ProjectData getProjectData(URL url) {
|
||||
if (projectLocator.getURL().equals(url)) {
|
||||
return fileMgr;
|
||||
return projectData;
|
||||
}
|
||||
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
|
||||
if (remoteURL != null) {
|
||||
remoteURL = GhidraURL.getProjectURL(url);
|
||||
}
|
||||
if (remoteURL.equals(url)) {
|
||||
return fileMgr;
|
||||
return projectData;
|
||||
}
|
||||
|
||||
synchronized (otherViews) {
|
||||
return otherViews.get(url);
|
||||
synchronized (otherViewsMap) {
|
||||
return otherViewsMap.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectData[] getViewedProjectData() {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
|
||||
// only return visible viewed project
|
||||
List<ProjectData> list = new ArrayList<>();
|
||||
for (URL url : otherViews.keySet()) {
|
||||
for (URL url : otherViewsMap.keySet()) {
|
||||
if (visibleViews.contains(url)) {
|
||||
list.add(otherViews.get(url));
|
||||
list.add(otherViewsMap.get(url));
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,11 +681,9 @@ public class DefaultProject implements Project {
|
||||
|
||||
@Override
|
||||
public void releaseFiles(Object consumer) {
|
||||
fileMgr.releaseDomainFiles(consumer);
|
||||
synchronized (otherViews) {
|
||||
Iterator<ProjectFileManager> it = otherViews.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
ProjectFileManager mgr = it.next();
|
||||
projectData.releaseDomainFiles(consumer);
|
||||
synchronized (otherViewsMap) {
|
||||
for (DefaultProjectData mgr : otherViewsMap.values()) {
|
||||
mgr.releaseDomainFiles(consumer);
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
|
||||
}
|
||||
catch (RepositoryNotFoundException e) {
|
||||
statusCode = StatusCode.NOT_FOUND;
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
else if (!repositoryServerAdapter.isCancelled()) {
|
||||
|
@ -21,7 +21,7 @@ import java.net.URL;
|
||||
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
|
||||
import ghidra.framework.store.LockException;
|
||||
@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
|
||||
* @return project data instance or null if project not found
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
||||
DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
||||
if (connect(readOnlyAccess) != StatusCode.OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false);
|
||||
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
|
||||
}
|
||||
catch (NotOwnerException | ReadOnlyException e) {
|
||||
statusCode = StatusCode.UNAUTHORIZED;
|
||||
|
@ -19,7 +19,7 @@ import java.io.*;
|
||||
import java.net.*;
|
||||
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
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.
|
||||
* @see GhidraURLWrappedContent
|
||||
@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
|
||||
|
||||
private GhidraProtocolConnector protocolConnector;
|
||||
|
||||
private ProjectFileManager projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private Object refObject;
|
||||
|
||||
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
|
||||
* responsible for closing the returned project data when no longer in-use,
|
||||
* failure to do so may prevent release of repository handle to server.
|
||||
* Only a single call to this method is permitted.
|
||||
* @return transient project data or null if unavailable
|
||||
* responsible for properly {@link ProjectData#close() closing} the returned project data
|
||||
* instance when no longer in-use, failure to do so may prevent release of repository handle
|
||||
* to server until process exits. It is important that {@link ProjectData#close()} is
|
||||
* 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
|
||||
*/
|
||||
public ProjectData getProjectData() throws IOException {
|
||||
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.InvalidNameException;
|
||||
|
||||
@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
|
||||
|
||||
private List<Object> consumers = new ArrayList<Object>();
|
||||
|
||||
private ProjectData projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private Object refObject;
|
||||
|
||||
public GhidraURLWrappedContent(GhidraURLConnection c) {
|
||||
@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (projectData != null) {
|
||||
@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
|
||||
refObject = null;
|
||||
}
|
||||
|
||||
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException,
|
||||
IOException {
|
||||
private DomainFolder getExplicitFolder(String folderPath)
|
||||
throws InvalidNameException, IOException {
|
||||
DomainFolder folder = projectData.getRootFolder();
|
||||
for (String name : folderPath.substring(1).split("/")) {
|
||||
DomainFolder subfolder = folder.getFolder(name);
|
||||
@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
|
||||
return;
|
||||
}
|
||||
|
||||
projectData = c.getProjectData();
|
||||
projectData = (DefaultProjectData) c.getProjectData();
|
||||
|
||||
String folderItemName = c.getFolderItemName();
|
||||
String folderPath = c.getFolderPath();
|
||||
|
@ -16,10 +16,11 @@
|
||||
package ghidra.framework.protocol.ghidra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import generic.timer.GhidraSwinglessTimer;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.remote.RepositoryHandle;
|
||||
import ghidra.framework.store.LockException;
|
||||
@ -27,7 +28,7 @@ import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class TransientProjectData extends ProjectFileManager {
|
||||
public class TransientProjectData extends DefaultProjectData {
|
||||
|
||||
private TransientProjectManager dataMgr;
|
||||
final RepositoryInfo repositoryInfo;
|
||||
@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
|
||||
}
|
||||
stopCleanupTimer();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " +
|
||||
getProjectLocator().getProjectDir());
|
||||
String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
|
||||
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);
|
||||
|
||||
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();
|
||||
FileUtilities.deleteDir(locator.getProjectDir());
|
||||
locator.getMarkerFile().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
public void close() {
|
||||
// prevent normal disposal - rely on finalizer and shutdown hook
|
||||
synchronized (cleanupTimer) {
|
||||
if (instanceUseCount == 0) {
|
||||
@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
forcedDispose();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// ignore errors during finalize
|
||||
}
|
||||
super.finalize();
|
||||
protected void dispose() {
|
||||
// rely on forcedDispose to invoke super.dispose()
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
return repositoryInfo.getURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -71,8 +71,9 @@ public class TransientProjectManager {
|
||||
}
|
||||
|
||||
private TransientProjectManager() {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook"));
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(new Thread((Runnable) () -> dispose(),
|
||||
"TransientProjectManager Shutdown Hook"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,8 +81,6 @@ public class TransientProjectManager {
|
||||
* connections. WARNING: This method intended for testing only.
|
||||
*/
|
||||
public synchronized void dispose() {
|
||||
// TODO: server handles may be shared with non-transient projects
|
||||
|
||||
TransientProjectData[] projectDataArray =
|
||||
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
|
||||
for (TransientProjectData projectData : projectDataArray) {
|
||||
|
@ -15,17 +15,16 @@
|
||||
*/
|
||||
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.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
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
|
||||
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
|
||||
@ -95,7 +94,8 @@ public class GTaskManager {
|
||||
|
||||
domainObject.addCloseListener(new DomainObjectClosedListener() {
|
||||
@Override
|
||||
public void domainObjectClosed() {
|
||||
public void domainObjectClosed(DomainObject dobj) {
|
||||
// assert dobj == domainObj
|
||||
GTaskManagerFactory.domainObjectClosed(domainObject);
|
||||
domainObject = null;
|
||||
}
|
||||
@ -107,7 +107,7 @@ public class GTaskManager {
|
||||
*
|
||||
* @param task the task to be run.
|
||||
* @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
|
||||
* will be closed and a new transaction will be opened before
|
||||
* this task is run.
|
||||
@ -680,7 +680,8 @@ public class GTaskManager {
|
||||
taskListener.taskCompleted(task, result);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
// stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
// stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
|
||||
// stub
|
||||
|
@ -36,7 +36,7 @@ import docking.wizard.WizardPanel;
|
||||
import generic.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.archive.RestoreDialog;
|
||||
import ghidra.framework.data.GhidraFileData;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.dialog.*;
|
||||
@ -694,7 +694,7 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
Project project = env.getProject();
|
||||
program = env.getProgram("WinHelloCPP.exe");
|
||||
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
|
||||
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||
projectData.getRootFolder().createFile("HelloCpp.exe", program, TaskMonitor.DUMMY);
|
||||
|
||||
// Create other project to be viewed
|
||||
|
@ -26,7 +26,7 @@ import org.junit.*;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.store.ItemCheckoutStatus;
|
||||
@ -276,7 +276,7 @@ public class ProjectInfoFilesystemTest extends AbstractGhidraHeadedIntegrationTe
|
||||
}
|
||||
|
||||
private void checkProjectInfo(Class<?> filesystemClass, String storageType) {
|
||||
ProjectFileManager projectData = (ProjectFileManager) project.getProjectData();
|
||||
DefaultProjectData projectData = (DefaultProjectData) project.getProjectData();
|
||||
String msg = "Expected " + filesystemClass.getSimpleName() + ": ";
|
||||
assertTrue(msg + "Local FileSystem", filesystemClass.isInstance(
|
||||
TestUtils.invokeInstanceMethod("getLocalFileSystem", projectData)));
|
||||
|
@ -802,9 +802,9 @@ public class ServerTestUtil {
|
||||
public static void createRepositoryItem(LocalFileSystem repoFilesystem, String name,
|
||||
String folderPath, Program program) throws Exception {
|
||||
|
||||
ContentHandler contentHandler = DomainObjectAdapter.getContentHandler(program);
|
||||
long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name,
|
||||
program, TaskMonitor.DUMMY);
|
||||
ContentHandler<?> contentHandler = DomainObjectAdapter.getContentHandler(program);
|
||||
long checkoutId = contentHandler.createFile(repoFilesystem, null, folderPath, name, program,
|
||||
TaskMonitor.DUMMY);
|
||||
LocalFolderItem item = repoFilesystem.getItem(folderPath, name);
|
||||
if (item == null) {
|
||||
throw new IOException("Item not found: " + FileSystem.SEPARATOR + name);
|
||||
|
Loading…
Reference in New Issue
Block a user