Merge branch 'GP-3551_ghidra1_InternalProjectLinks'
|
@ -15,6 +15,43 @@ applied Ghidra SRE capabilities to a variety of problems that involve analyzing
|
|||
generating deep insights for NSA analysts who seek a better understanding of potential
|
||||
vulnerabilities in networks and systems.
|
||||
|
||||
# What's coming in Ghidra 11.5
|
||||
This is a preview of what is coming in the future Ghidra 11.5 release.
|
||||
|
||||
**NOTE:** Ghidra Server: The Ghidra 11.5 server is compatible with Ghidra 9.2 and later Ghidra
|
||||
clients although the presence of any newer link-files within a repository may not be handled properly
|
||||
by client versions prior to 11.5 which lack support for the new storage format. Ghidra 11.5 clients
|
||||
which introduce new link-files into a project will not be able to add such files into version
|
||||
control if connected to older Ghidra Server versions.
|
||||
|
||||
## Project Link Files
|
||||
|
||||
Support for link-files within a Ghidra Project has been significantly expanded with this release and
|
||||
with it a new file storage type has been introduced which can create some incompatibilties if projects
|
||||
and repositories containing such files are used by older version of Ghidra or the Ghidra Server.
|
||||
|
||||
Previously only external folder and file links were supported through the use of a Ghidra URL.
|
||||
With 11.5 the ability to establish internal folder and file links has been introduced. The new
|
||||
storage format avoids the use of a database and relies only on a light-weight property file. Internal
|
||||
project links also allow for either absolute or relative links. Due to the fact that Ghidra allows
|
||||
a folder or file to have the same pathname, some abiguities can result. It is highly recommended that
|
||||
the use of conflicting folder and file pathnames be avoided.
|
||||
|
||||
The use of internally linked folders and files allows batch import processing to more accurately
|
||||
reflect the native file-system and its use of symbolic links which allow for the same content to
|
||||
be referenced by multiple paths. Allowing this within a Ghidra project can avoid the potential for
|
||||
importing content multiple times with the different paths and simply import once with additional
|
||||
link-files which reference it. How best to leverage links very much depends on the end-user's
|
||||
needs and project file management preferences. Special care must be taken when defining or
|
||||
traversing link-files to avoid external and circular references.
|
||||
|
||||
Additional Ghidra API methods have been provided or refined on the following classes to leverage
|
||||
link-files: `DomainFolder`, `DomainFile`, `LinkFile`, `LinkHandler`, `DomainFileFilter`,
|
||||
`DomainFileIterator`, etc.
|
||||
|
||||
...TO BE CONTINUED...
|
||||
|
||||
|
||||
# What's New in Ghidra 11.4
|
||||
This release includes new features, enhancements, performance improvements, quite a few bug fixes,
|
||||
and many pull-request contributions. Thanks to all those who have contributed their time, thoughts,
|
||||
|
|
|
@ -116,10 +116,8 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin {
|
|||
? view.getTrace().getFixedProgramView(view.getSnap())
|
||||
: view;
|
||||
|
||||
ExporterDialog dialog =
|
||||
new ExporterDialog(tool, fixed.getDomainFile(), fixed,
|
||||
ExporterDialog.showExporterDialog(tool, fixed.getDomainFile(), fixed,
|
||||
getSelectionFromContext(context));
|
||||
tool.showDialog(dialog);
|
||||
}
|
||||
|
||||
protected void activatedCopyIntoCurrentProgram(DebuggerProgramLocationActionContext context) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import ghidra.app.plugin.core.debug.utils.ProgramURLUtils;
|
|||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.trace.model.Lifespan;
|
||||
|
@ -73,7 +74,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
|||
// TODO: Note language and prefer those from the same processor?
|
||||
// Will get difficult with new OBTR, since I'd need a platform
|
||||
// There's also the WoW64 issue....
|
||||
protected record IndexEntry(String name, String dfID, NameSource source) {}
|
||||
protected record IndexEntry(String name, String dfID, NameSource source) {
|
||||
}
|
||||
|
||||
protected class ModuleChangeListener
|
||||
implements DomainObjectListener, DomainObjectClosedListener {
|
||||
|
@ -212,11 +214,14 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
|||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
if (!Program.class.isAssignableFrom(file.getDomainObjectClass())) {
|
||||
return;
|
||||
}
|
||||
// Folder-links and program link-files are not handled. Using content type
|
||||
// to filter is the best way to control this. If program links should be considered
|
||||
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
||||
// should be used.
|
||||
if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||
addToIndex(file, file.getMetadata());
|
||||
}
|
||||
}
|
||||
|
||||
protected void addToIndex(DomainFile file, Map<String, String> metadata) {
|
||||
String dfID = file.getFileID();
|
||||
|
@ -383,8 +388,8 @@ public class ProgramModuleIndexer implements DomainFolderChangeListener {
|
|||
public DomainFile getBestMatch(TraceModule module, long snap, Program currentProgram,
|
||||
Collection<IndexEntry> entries) {
|
||||
Address base = module.getBase(snap);
|
||||
AddressSpace space = base == null
|
||||
? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
|
||||
AddressSpace space =
|
||||
base == null ? module.getTrace().getBaseAddressFactory().getDefaultAddressSpace()
|
||||
: base.getAddressSpace();
|
||||
return getBestMatch(space, module, snap, currentProgram, entries);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.debug.service.tracemgr;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.OPEN;
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
|
@ -223,9 +223,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
public void targetWithdrawn(Target target) {
|
||||
Swing.runLater(() -> updateCurrentTarget());
|
||||
boolean save = isSaveTracesByDefault();
|
||||
CompletableFuture<Void> flush = save
|
||||
? waitUnlockedDebounced(target)
|
||||
: AsyncUtils.nil();
|
||||
CompletableFuture<Void> flush = save ? waitUnlockedDebounced(target) : AsyncUtils.nil();
|
||||
flush.thenRunAsync(() -> {
|
||||
if (!isAutoCloseOnTerminate()) {
|
||||
return;
|
||||
|
@ -416,20 +414,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
}
|
||||
|
||||
protected DataTreeDialog getTraceChooserDialog() {
|
||||
|
||||
DomainFileFilter filter = new DomainFileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(DomainFile df) {
|
||||
return Trace.class.isAssignableFrom(df.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean followLinkedFolders() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
DomainFileFilter filter = new DefaultDomainFileFilter(Trace.class, false);
|
||||
return new DataTreeDialog(null, OpenTraceAction.NAME, OPEN, filter);
|
||||
}
|
||||
|
||||
|
@ -454,11 +439,8 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public void closeDeadTraces() {
|
||||
checkCloseTraces(targetService == null
|
||||
? getOpenTraces()
|
||||
: getOpenTraces().stream()
|
||||
.filter(t -> targetService.getTarget(t) == null)
|
||||
.toList(),
|
||||
checkCloseTraces(targetService == null ? getOpenTraces()
|
||||
: getOpenTraces().stream().filter(t -> targetService.getTarget(t) == null).toList(),
|
||||
false);
|
||||
}
|
||||
|
||||
|
@ -790,8 +772,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
varView.setSnap(snap);
|
||||
varView.setPlatform(coordinates.getPlatform());
|
||||
fireLocationEvent(coordinates, cause);
|
||||
}, cause == ActivationCause.EMU_STATE_EDIT
|
||||
? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
|
||||
}, cause == ActivationCause.EMU_STATE_EDIT ? SwingExecutorService.MAYBE_NOW // ProgramView may call .get on Swing thread
|
||||
: SwingExecutorService.LATER); // Respect event order
|
||||
}
|
||||
|
||||
|
@ -845,7 +826,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
// TODO: Support upgrading
|
||||
e = new VersionException(e.getVersionIndicator(), false).combine(e);
|
||||
VersionExceptionHandler.showVersionError(null, file.getName(), file.getContentType(),
|
||||
"Open", e);
|
||||
"Open", false, e);
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
@ -1069,10 +1050,7 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
|
|||
|
||||
protected void checkCloseTraces(Collection<Trace> traces, boolean noConfirm) {
|
||||
List<Target> live =
|
||||
traces.stream()
|
||||
.map(t -> targetService.getTarget(t))
|
||||
.filter(t -> t != null)
|
||||
.toList();
|
||||
traces.stream().map(t -> targetService.getTarget(t)).filter(t -> t != null).toList();
|
||||
/**
|
||||
* A provider may be reading a trace, likely via the Swing thread, so schedule this on the
|
||||
* same thread to avoid a ClosedException.
|
||||
|
|
|
@ -313,7 +313,7 @@ public class DBTraceContentHandler extends DBWithUserDataContentHandler<DBTrace>
|
|||
|
||||
@Override
|
||||
public String getContentTypeDisplayString() {
|
||||
return "Trace";
|
||||
return TRACE_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,32 +15,15 @@
|
|||
*/
|
||||
package ghidra.trace.database;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import ghidra.framework.data.LinkHandler;
|
||||
import ghidra.framework.data.URLLinkObject;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DBTraceLinkContentHandler extends LinkHandler<DBTrace> {
|
||||
|
||||
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
|
||||
public static DBTraceLinkContentHandler INSTANCE = new DBTraceLinkContentHandler();
|
||||
|
||||
@Override
|
||||
public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
|
||||
DomainObject obj, TaskMonitor monitor)
|
||||
throws IOException, InvalidNameException, CancelledException {
|
||||
if (!(obj instanceof URLLinkObject)) {
|
||||
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
|
||||
}
|
||||
return createFile((URLLinkObject) obj, TRACE_LINK_CONTENT_TYPE, fs, path, name,
|
||||
monitor);
|
||||
}
|
||||
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.machinelearning.functionfinding;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -35,7 +33,7 @@ import docking.widgets.table.GTable;
|
|||
import docking.widgets.table.threaded.GThreadedTablePanel;
|
||||
import docking.widgets.textfield.IntegerTextField;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.ProgramFileChooser;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
|
@ -485,11 +483,7 @@ public class FunctionStartRFParamsDialog extends ReusableDialogComponentProvider
|
|||
}
|
||||
|
||||
private void searchOtherProgram(RandomForestRowObject modelRow) {
|
||||
DataTreeDialog dtd =
|
||||
new DataTreeDialog(null, "Select Program", OPEN, f -> {
|
||||
Class<?> c = f.getDomainObjectClass();
|
||||
return Program.class.isAssignableFrom(c);
|
||||
});
|
||||
ProgramFileChooser dtd = new ProgramFileChooser(null, "Select Program");
|
||||
dtd.show();
|
||||
DomainFile dFile = dtd.getDomainFile();
|
||||
if (dFile == null) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.net.URL;
|
|||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.protocol.ghidra.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
@ -56,7 +57,8 @@ public abstract class IterateRepository {
|
|||
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
|
||||
}
|
||||
|
||||
GhidraURLQuery.queryUrl(ghidraURL, new GhidraURLResultHandlerAdapter(true) {
|
||||
// Query URL - may be either file or folder (no link following)
|
||||
GhidraURLQuery.queryUrl(ghidraURL, null, new GhidraURLResultHandlerAdapter(true) {
|
||||
|
||||
@Override
|
||||
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor m)
|
||||
|
@ -76,7 +78,9 @@ public abstract class IterateRepository {
|
|||
process(domainFile, monitor);
|
||||
}
|
||||
|
||||
}, monitor);
|
||||
// Link files are skipped to avoid duplicate processing
|
||||
// Processing should be done on actual folder - not a linked folder
|
||||
}, LinkFileControl.NO_FOLLOW, monitor);
|
||||
|
||||
}
|
||||
|
||||
|
@ -115,12 +119,11 @@ public abstract class IterateRepository {
|
|||
private void process(DomainFile file, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
// Do not follow folder-links or consider program links. Using content type
|
||||
// to filter is best way to control this. If program links should be considered
|
||||
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
||||
// Do not follow folder-links or consider program links to avoid possible duplication of
|
||||
// file processing. Using content type is the best way to restrict this. If program links
|
||||
// should be considered "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
|
||||
// should be used.
|
||||
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||
// NOTE: linked-folders and linked-files are not currently supported
|
||||
return; // skip non-program file
|
||||
}
|
||||
|
||||
|
@ -129,6 +132,7 @@ public abstract class IterateRepository {
|
|||
Msg.debug(IterateRepository.class, "Processing " + file.getPathname() + "...");
|
||||
monitor.setMessage("Processing: " + file.getName());
|
||||
monitor.incrementProgress(1);
|
||||
// NOTE: The following method invocation will follow all links if presented one
|
||||
program = (Program) file.getReadOnlyDomainObject(this, -1, monitor);
|
||||
process(program, monitor);
|
||||
}
|
||||
|
|
|
@ -398,6 +398,10 @@ src/main/help/help/topics/FrontEndPlugin/Project_Info.htm||GHIDRA||||END|
|
|||
src/main/help/help/topics/FrontEndPlugin/Re-opening_a_Project.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/Restore_Project.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/Saving_a_Ghidra_Project.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFileLinkIcon.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteBrokenFolderLinkIcon.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFileLinkIcon.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/AbsoluteFolderLinkIcon.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ArchiveFileExists.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ArchiveProject.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ChangeAccessList.png||GHIDRA||||END|
|
||||
|
@ -439,6 +443,7 @@ src/main/help/help/topics/FrontEndPlugin/images/VersionedFileIcon.png||GHIDRA|||
|
|||
src/main/help/help/topics/FrontEndPlugin/images/ViewOtherProjects.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/ViewProjectAccessPanel.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/hijack_file.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FrontEndPlugin/images/start-here_16.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionComparison/FunctionComparison.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionComparison/images/AddFunctionsPanel.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/FunctionComparison/images/AddToComparisonIcon.png||GHIDRA||||END|
|
||||
|
|
|
@ -30,7 +30,7 @@ import ghidra.app.script.ImproperUseException;
|
|||
import ghidra.framework.data.GhidraFile;
|
||||
import ghidra.framework.data.GhidraFileData;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.store.FolderItem;
|
||||
import ghidra.framework.store.local.LocalDatabaseItem;
|
||||
import ghidra.program.model.lang.LanguageDescription;
|
||||
|
@ -115,8 +115,8 @@ public class FixLangId extends GhidraScript {
|
|||
if (langId != null) {
|
||||
Msg.warn(this, "Changing language ID from '" + record.getString(0) + "' to '" +
|
||||
langId + "' for program: " + df.getName());
|
||||
desc = DefaultLanguageService.getLanguageService().getLanguageDescription(
|
||||
new LanguageID(langId));
|
||||
desc = DefaultLanguageService.getLanguageService()
|
||||
.getLanguageDescription(new LanguageID(langId));
|
||||
long txId = dbh.startTransaction();
|
||||
try {
|
||||
record.setString(0, langId);
|
||||
|
@ -139,7 +139,10 @@ public class FixLangId extends GhidraScript {
|
|||
|
||||
public DomainFile askProgramFile(String title) {
|
||||
final DomainFile[] domainFile = new DomainFile[] { null };
|
||||
final DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
||||
// The file filter employed restricts selection to a program file within the active
|
||||
// project where we have the ability to update file data.
|
||||
final DataTreeDialog dtd =
|
||||
new DataTreeDialog(null, title, OPEN, new DefaultDomainFileFilter(Program.class, true));
|
||||
dtd.addOkActionListener(e -> {
|
||||
dtd.close();
|
||||
domainFile[0] = dtd.getDomainFile();
|
||||
|
|
|
@ -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,7 +19,9 @@
|
|||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -29,8 +30,9 @@ public class RenameProgramsInProjectScript extends GhidraScript {
|
|||
@Override
|
||||
public void run() throws Exception {
|
||||
|
||||
if ( currentProgram != null ) {
|
||||
popup( "This script should be run from a tool with no open programs" );
|
||||
if (currentProgram != null) {
|
||||
popup("This script should be run from a tool with no open programs.\n" +
|
||||
"Warning! If using file-links to programs within this project such linkages will break.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -38,29 +40,34 @@ public class RenameProgramsInProjectScript extends GhidraScript {
|
|||
Project project = tool.getProject();
|
||||
ProjectData projectData = project.getProjectData();
|
||||
DomainFolder rootFolder = projectData.getRootFolder();
|
||||
recurseProjectFolder( rootFolder );
|
||||
recurseProjectFolder(rootFolder);
|
||||
}
|
||||
|
||||
private void recurseProjectFolder( DomainFolder domainFolder ) {
|
||||
private void recurseProjectFolder(DomainFolder domainFolder) throws CancelledException {
|
||||
DomainFile[] files = domainFolder.getFiles();
|
||||
for ( DomainFile domainFile : files ) {
|
||||
processDomainFile( domainFile );
|
||||
for (DomainFile domainFile : files) {
|
||||
monitor.checkCancelled();
|
||||
processDomainFile(domainFile);
|
||||
}
|
||||
DomainFolder[] folders = domainFolder.getFolders();
|
||||
for ( DomainFolder folder : folders ) {
|
||||
recurseProjectFolder( folder );
|
||||
for (DomainFolder folder : folders) {
|
||||
monitor.checkCancelled();
|
||||
recurseProjectFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDomainFile( DomainFile domainFile ) {
|
||||
private void processDomainFile(DomainFile domainFile) {
|
||||
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) {
|
||||
return;
|
||||
}
|
||||
String oldName = domainFile.getName();
|
||||
try {
|
||||
domainFile.setName( oldName + "_renamed" );
|
||||
domainFile.setName(oldName + "_renamed");
|
||||
}
|
||||
catch ( InvalidNameException e ) {
|
||||
catch (InvalidNameException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,6 +102,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
|
|||
}
|
||||
|
||||
private int listCheckouts(DomainFolder folder) throws IOException, CancelledException {
|
||||
// Avoid following folder-links so we don't count the same file more than once.
|
||||
// Link-files will never be in a checked-out state.
|
||||
int count = 0;
|
||||
for (DomainFile df : folder.getFiles()) {
|
||||
monitor.checkCancelled();
|
||||
|
@ -115,8 +117,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
|
|||
}
|
||||
|
||||
private int listCheckouts(DomainFile df) throws IOException {
|
||||
if (!df.isVersioned()) {
|
||||
return 0;
|
||||
if (!df.isVersioned() || df.isLink()) {
|
||||
return 0; // ignore non-versioned files and link-files
|
||||
}
|
||||
int count = 0;
|
||||
for (ItemCheckoutStatus checkout : df.getCheckouts()) {
|
||||
|
|
|
@ -27,7 +27,6 @@ public class VersionControl_ResetAll extends GhidraScript {
|
|||
public VersionControl_ResetAll() {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
|
||||
|
@ -54,7 +53,8 @@ public class VersionControl_ResetAll extends GhidraScript {
|
|||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Do not follow folder-links or consider program links. Checking the content type
|
||||
// is the best way to restrict this.
|
||||
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) ||
|
||||
!file.isVersioned() || file.getLatestVersion() < 2) {
|
||||
continue;// skip
|
||||
|
|
|
@ -61,6 +61,8 @@
|
|||
|
||||
<LI><A href="#FileIcons">File Icons</A></LI>
|
||||
|
||||
<LI><A href="#GhidraURLFormats">Ghidra URL Formats</A></LI>
|
||||
|
||||
<LI><A href="#StatusWindow">Console</A></LI>
|
||||
|
||||
<LI><A href="Ghidra_Front_end_Menus.htm#Configure">Configure Project Window</A></LI>
|
||||
|
@ -93,8 +95,10 @@
|
|||
<H2><A name="ActiveProjectPanel"></A>Active Project</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Active Project view shows your programs and datatype archives in a tree view or a
|
||||
table view. The tree view is useful for organizing your files into folders and sub-folders.
|
||||
<P>The Active Project view shows the various files associated with the current
|
||||
project which has been open for update. Project files generally consist of programs and
|
||||
datatype archives but may also be related to other Ghidra content.
|
||||
The tree view is useful for organizing your files into folders and sub-folders.
|
||||
The table view is useful for sorting all your files on some particular attribute such as
|
||||
size, processor, or modification date. In either view, you open and perform various
|
||||
actions on program files or datatype archives.</P>
|
||||
|
@ -105,13 +109,23 @@
|
|||
</CENTER>
|
||||
<BLOCKQUOTE>
|
||||
<P>The data tree shows all files in the project orgnanized into folders and sub-folders.
|
||||
<A href="#FileIcons">Icons for files</A>
|
||||
indicate whether they are under <A href=
|
||||
<A href="#FileIcons">Icons for files</A> indicate whether they are under <A href=
|
||||
"help/topics/VersionControl/project_repository.htm#Versioning">version control</A> and whether
|
||||
you have the file <A href=
|
||||
"help/topics/VersionControl/project_repository.htm#SampleCheckOutIcon">checked out</A>.
|
||||
Open this view by activating the "Tree View" tab.</P>
|
||||
In addition, unique icons are used to reflect content-type and if it corresponds to
|
||||
a link-file referring to another file or folder (see <A href="#Paste_Link">creating links</A>).
|
||||
Open this view by activating the project window "Tree View" tab.</P>
|
||||
|
||||
<P><IMG src="help/shared/tip.png" border="0">Although Ghidra allows a folder and file within
|
||||
the same parent folder to have the same name, it is recommended this be avoided if possible.
|
||||
Allowing both a folder and file to have the same pathname can result in ambiguous path problems
|
||||
when using link files and/or Ghidra URLs where only a path is used to identify either a project
|
||||
resource.
|
||||
</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P> </P>
|
||||
<H3>Tree Only Actions</H3>
|
||||
|
||||
|
@ -124,7 +138,7 @@
|
|||
<P>To create a new folder,</P>
|
||||
|
||||
<OL>
|
||||
<LI>Select a folder that you own. </LI>
|
||||
<LI>Select a folder which should contain the new folder.</LI>
|
||||
|
||||
<LI>Right mouse click and choose the <I>New Folder</I> option.</LI>
|
||||
|
||||
|
@ -133,8 +147,6 @@
|
|||
editing.</LI>
|
||||
</OL>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> You cannot create
|
||||
a sub-folder of a folder that you do not own.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="Copy"></A><A name="Paste"></A>Copy Folders and Files</H4>
|
||||
|
@ -160,6 +172,46 @@
|
|||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="Paste_Link"></A><A name="Paste_Relative_Link"></A>Paste Copied Folder or File as a Link</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>A Link may be created within the active project to a file or folder within the
|
||||
same project (internal) or to a viewed project/repository (external).
|
||||
Internal links may be defined using either a relative path or an absolute path. Once
|
||||
a link is created its stored path will not change. The link will need to be replaced
|
||||
should the referenced path need to be changed. In addition, file-links are specific
|
||||
to the content-type of the referenced file at the time of link creation (e.g.,
|
||||
ProgramLink).
|
||||
</P>
|
||||
<P>To create a Link use the following steps from the source project data tree:</P>
|
||||
<OL>
|
||||
<LI>Select a single file or folder, right mouse click and choose the <I>Copy</I> option.</LI>
|
||||
|
||||
<LI>Select a destination folder within the active project data tree.</LI>
|
||||
|
||||
<LI>Right mouse click and choose the <I>Paste as Link</I> or <I>Paste as Relative-Link</I>
|
||||
option.</LI>
|
||||
</OL>
|
||||
|
||||
<P>See <A href="#Create_File_Links">Create Linked Folder or File</A> for more information
|
||||
about links and creating external links.
|
||||
</P>
|
||||
|
||||
<P>An internal link in the project tree may indicate a "broken" status for
|
||||
various reasons, including:</P>
|
||||
<ul>
|
||||
<li>The referenced file or folder does not exist,</li>
|
||||
<li>the content-type at the referenced location does not match the link type, or</li>
|
||||
<li>a folder-link results in a circular path reference.</li>
|
||||
</ul>
|
||||
<P>A broken link will have an icon which conveys its type but with a jagged red line
|
||||
through it and a tooltip which conveys the issue detected.</P>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0">External links will never show a broken
|
||||
link state since they are not evaluated for such conditions.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="Cut"></A>Move Folders and Files</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
@ -182,7 +234,7 @@
|
|||
</OL>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0">You cannot move a
|
||||
file that is in use.</P>
|
||||
file that is in use or a folder that contains a file that is in use.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Drag/Drop for Copy</H4>
|
||||
|
@ -217,6 +269,9 @@
|
|||
|
||||
<LI>Release the mouse button when you get a valid drop target.</LI>
|
||||
</OL>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0">You cannot move a
|
||||
file that is in use or a folder that contains a file that is in use.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> If a folder or file
|
||||
|
@ -242,6 +297,24 @@
|
|||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4><A name="Follow_Link"></A>Follow Link</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Select the internal or external folder or file referenced by a selected link-file.
|
||||
While internal folders may be expanded directly from a folder-link, following a link
|
||||
to the actual referenced location may be useful at times.
|
||||
</P>
|
||||
|
||||
<OL>
|
||||
<LI>
|
||||
Select a file-link or folder-link, right mouse click and choose the <I>Follow Link</I>
|
||||
option. The referenced file or folder will be selected if possible. If associated
|
||||
with an external project or repository the selection will occur in a READ-ONLY
|
||||
project view once opened.</LI>
|
||||
</OL>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<P> </P>
|
||||
|
||||
|
@ -421,9 +494,9 @@
|
|||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||
</TD>
|
||||
|
||||
<TD style="vertical-align: top;">A <A href=
|
||||
<TD style="vertical-align: top;"><A href=
|
||||
"help/topics/Program/Ghidra_Programs.htm"><SPAN style=
|
||||
"font-weight: bold;">program</SPAN></A><BR>
|
||||
"font-weight: bold;">Program</SPAN></A><BR>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
|
@ -434,12 +507,34 @@
|
|||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||
</TD>
|
||||
|
||||
<TD style="vertical-align: top;">A <A href=
|
||||
<TD style="vertical-align: top;"><A href=
|
||||
"help/topics/DataTypeManagerPlugin/data_type_manager_description.htm#ProjectDataTypeArchive"><SPAN
|
||||
style="font-weight: bold;">project data type archive</SPAN></A> (a data type file
|
||||
style="font-weight: bold;">Data Type Archive</SPAN></A> (a data type file
|
||||
stored in the project)<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
|
||||
"images/video-x-generic16.png"></TD>
|
||||
|
||||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||
</TD>
|
||||
|
||||
<TD style="vertical-align: top;">Debugger Trace Data<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD style="vertical-align: top; width: 20px;"><IMG alt="" src=
|
||||
"images/start-here_16.png"></TD>
|
||||
|
||||
<TD style="vertical-align: top; width: 10px;">-<BR>
|
||||
</TD>
|
||||
|
||||
<TD style="vertical-align: top;">Version Tracking Session Data<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
|
||||
|
@ -522,6 +617,61 @@
|
|||
other users.</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD width="25%">File Link </TD>
|
||||
|
||||
<TD width="20%"><IMG src="images/AbsoluteFileLinkIcon.png" border="0"></TD>
|
||||
|
||||
<TD width="40%"><A name="FileLink"></A>A file link named "Example" which refers to
|
||||
a Program at <I>/data/example</I>. File links may reference another file using either an
|
||||
1) absolute file path within the same project, 2) a relative file path within
|
||||
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
|
||||
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
|
||||
A file link may appear with various icon states which correspond to version control.
|
||||
File links only support a single version and may not be modified.
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD width="25%">File Link (Broken) </TD>
|
||||
|
||||
<TD width="20%"><IMG src="images/AbsoluteBrokenFileLinkIcon.png" border="0"></TD>
|
||||
|
||||
<TD width="40%"><A name="BrokenFileLink"></A>A file link named "Example" which refers to
|
||||
a Program at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
|
||||
on this node will display a tooltip which indicates the reason for the broken state.
|
||||
External file links will never show a broken link state since they are not evaluated for such conditions.
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD width="25%">Folder Link </TD>
|
||||
|
||||
<TD width="20%"><IMG src="images/AbsoluteFolderLinkIcon.png" border="0"></TD>
|
||||
|
||||
<TD width="40%"><A name="FolderLink"></A>A folder link named "Example" which refers
|
||||
to a folder at <I>/data/example</I>. Folder links may reference another folder using either an
|
||||
1) absolute file path within the same project, 2) a relative file path within
|
||||
the same project, 3) a shared repository Ghidra URL, or 4) a local project Ghidra URL.
|
||||
See <A href="#GhidraURLFormats">Ghidra URL formats</A> below.
|
||||
Since a folder link is stored as a file, it may appear with various icon states which
|
||||
correspond to version control. Folder links only support a single version and may not
|
||||
be modified.
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD width="25%">Folder Link (Broken) </TD>
|
||||
|
||||
<TD width="20%"><IMG src="images/AbsoluteBrokenFolderLinkIcon.png" border="0"></TD>
|
||||
|
||||
<TD width="40%"><A name="BrokenFolderLink"></A>A folder link named "Example" which refers to
|
||||
a folder at <I>/data/example</I> and is in a "Broken" state. Hovering the mouse
|
||||
on this node will display a tooltip which indicates the reason for the broken state.
|
||||
External folder links will never show a broken link state since they are not evaluated for such conditions.
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD width="25%">Hijacked File</TD>
|
||||
|
||||
|
@ -545,9 +695,44 @@
|
|||
</TABLE>
|
||||
</CENTER>
|
||||
</DIV>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<H3> </H3>
|
||||
<H3><A name="GhidraURLFormats"></A>Ghidra URL Formats</H3>
|
||||
|
||||
<P>The format of a remote <EM>Ghidra Server URL</EM> is distinctly different from a
|
||||
<EM>Local Ghidra Project URL</EM>. These URLs have the following formats:</P>
|
||||
|
||||
<P><STRONG>Remote Ghidra Server Repository</STRONG><BR>
|
||||
</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<TABLE border="0" class="simplelist">
|
||||
<TR>
|
||||
<TD><CODE>ghidra://<hostname>[:<port>]/<repository_name>[/<folder_or_file_path>]</CODE></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>If the default Ghidra Server port (13100) is in use it is not specified by the URL.
|
||||
The <EM>hostname</EM> may specify either a Fully Qualified Domain Name (FQDN, e.g.,
|
||||
<EM>host.abc.com</EM>) or IP v4 Address (e.g., <EM>1.2.3.4</EM>).</P>
|
||||
|
||||
<P><STRONG>Local Ghidra Project</STRONG><BR>
|
||||
</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<TABLE border="0" class="simplelist">
|
||||
<TR>
|
||||
<TD><CODE>ghidra:[/<directory_path>]/<project_name>[?/<folder_or_file_path>]</CODE></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P>For local project URLs, the absolute directory path containing the project
|
||||
<EM>*.gpr</EM> locator file is specified with the project name but excludes any <EM>.gpr/.rep</EM> suffix.
|
||||
The folder or file path within the project is conveyed with a URL query so the '?' is required.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="ReadOnlyProjectDataPanel"></A>Read-Only Project Data</H2>
|
||||
|
@ -697,47 +882,54 @@
|
|||
|
||||
<BLOCKQUOTE>
|
||||
<P>This feature allows you to create a folder or file link in your active project to a
|
||||
corresponding folder or file within a read-only viewed project.
|
||||
This is done using a Ghidra URL which references the
|
||||
file in its local or remote storage location. If the viewed project corresponds to a
|
||||
viewed repository a remote URL will be used, while other cases will refer to the
|
||||
locally viewed project. It is possible for links to become broken if the referenced
|
||||
repository, local project or file location are changed.</P>
|
||||
corresponding folder or file within your project or to a read-only viewed project.
|
||||
External links are established using a Ghidra URL which references a
|
||||
file or folder in its local or remote storage location. An external Ghidra URL will
|
||||
be used if a link refers to a viewed project or repository. It is possible for internal links to
|
||||
become broken if the referenced file or folder location has changed (e.g., no longers exists
|
||||
or has the wrong content type). External links may become invalid for various reasons
|
||||
but will not convey an issue until the link is used. The broken link icon does not apply
|
||||
to external link files.
|
||||
</P>
|
||||
<P>To create an external folder or file link the following steps may be used:</P>
|
||||
<ol>
|
||||
<li>Select a single folder or file from a viewed READ-ONLY Project Data tree.</li>
|
||||
<li>Right mouse click on the selected tree node and choose the <I>Copy</I> option.</li>
|
||||
<li>Select a destination folder in the active project tree.</li>
|
||||
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.
|
||||
<P><IMG src="help/shared/note.png" border="0">Currently, linked-file types are
|
||||
currently limited to <I>Program</I> and <I>Data Type Archive</I> files
|
||||
only. The <I>Past as Link</I> menu item will be disabled for
|
||||
unsupported file content types or for other unsupported situations such as internal
|
||||
linking within the same project.</P>
|
||||
</li>
|
||||
<li>Select a destination folder in the active project data tree.</li>
|
||||
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.</li>
|
||||
</ol>
|
||||
<P>A linked-file may be opened in a tool via the project window in the same fashion that
|
||||
<P>It is important to note that the resulting link is always stored as a file within the
|
||||
project. With the exception of external links to local project content, a link may be
|
||||
added to version control so that it may be shared. Once added to version control it cannot
|
||||
be checked-out, since they are immutable, however they can still be deleted.</P>
|
||||
<P>A file-link may be opened in a tool via the project window in the same fashion that
|
||||
a normal file is opened (e.g., double-left-mouse-click or drag-n-drop onto a tool box icon).
|
||||
Such a project file may also be opened within a Tool using its <B>File->Open...</B> action
|
||||
and selected from the resulting project file selection dilaog.
|
||||
Clicking on a linked-folder in the active project window will open that location in a
|
||||
Clicking on an external folder-link in the active project window will open that location in a
|
||||
<B>READ-ONLY Project Data</B> tree. The user may be prompted for a shared repository
|
||||
connection password when accessing a linked folder or file.</P>
|
||||
<P>Within a project file chooser dialog a linked-folder may be expanded in a similar fashion
|
||||
connection password when accessing an external folder or file link.</P>
|
||||
<P>Within a project file chooser dialog a folder-link may be expanded in a similar fashion
|
||||
to local folders provided any neccessary repository connection can be completed.</P>
|
||||
<P><IMG src="help/shared/note.png" border="0"><B>Add to Version Control...</B> is supported
|
||||
for repository folder and file links only and will be disabled for links to a
|
||||
local project.</P>
|
||||
<P><IMG src="help/shared/note.png" border="0">Currently, linked-files only provide access
|
||||
to the latest file version and do not facilitate access to older file versions.</P>
|
||||
<P><IMG src="help/shared/note.png" border="0">Currently, external file-links only provide access
|
||||
to the latest file version and do not facilitate access to older file versions. An external
|
||||
folder-link will allow access to file versions contained within such a folder.
|
||||
</P>
|
||||
<P><IMG src="help/shared/note.png" border="0">Some file chooser use cases, including the
|
||||
<I>GhidraScript</I> API, are restricted to selecting files and folders within the active
|
||||
project only and will hide all external links.
|
||||
</P>
|
||||
<P>The project window below shows a Program file-link "Program1" which is linked to the
|
||||
same file in the viewed project. Hovering the mouse over a linked-file will show the URL
|
||||
of the linked file or folder. The chain-link icon decoration indicates such a linked
|
||||
file or folder.</P>
|
||||
same file in the viewed project.</P>
|
||||
|
||||
<CENTER>
|
||||
<IMG src= "images/LinkOtherProject.png" border="0">
|
||||
</CENTER>
|
||||
|
||||
<P>A folder or file link will show its referenced location with either
|
||||
same file in the viewed project.</P>
|
||||
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1,003 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 658 B |
|
@ -133,8 +133,9 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
|
|||
OPTION_DESCRIPTION_GDT_FILEPATH,
|
||||
() -> new FileChooserEditor(FileDataTypeManager.GDT_FILEFILTER));
|
||||
options.registerOption(OPTION_NAME_PROJECT_PATH, OptionType.STRING_TYPE, null, null,
|
||||
OPTION_DESCRIPTION_PROJECT_PATH, () -> new ProjectPathChooserEditor(
|
||||
"Choose Data Type Archive", DATATYPEARCHIVE_PROJECT_FILTER));
|
||||
OPTION_DESCRIPTION_PROJECT_PATH,
|
||||
() -> new ProjectPathChooserEditor("Choose Data Type Archive",
|
||||
new DefaultDomainFileFilter(DataTypeArchive.class, false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -289,6 +290,4 @@ public class ApplyDataArchiveAnalyzer extends AbstractAnalyzer {
|
|||
.collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
}
|
||||
|
||||
private static final DomainFileFilter DATATYPEARCHIVE_PROJECT_FILTER =
|
||||
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
|
||||
}
|
||||
|
|
|
@ -46,10 +46,6 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
|
|||
private String title;
|
||||
private DomainFileFilter filter;
|
||||
|
||||
public ProjectPathChooserEditor() {
|
||||
this(null, null);
|
||||
}
|
||||
|
||||
public ProjectPathChooserEditor(String title, DomainFileFilter filter) {
|
||||
this.title = title;
|
||||
this.filter = filter;
|
||||
|
@ -127,8 +123,7 @@ public class ProjectPathChooserEditor extends PropertyEditorSupport {
|
|||
|
||||
private void displayFileChooser() {
|
||||
AtomicReference<String> result = new AtomicReference<>();
|
||||
DataTreeDialog dataTreeDialog =
|
||||
new DataTreeDialog(this, title, OPEN, filter);
|
||||
DataTreeDialog dataTreeDialog = new DataTreeDialog(this, title, OPEN, filter);
|
||||
dataTreeDialog.addOkActionListener(e -> {
|
||||
dataTreeDialog.close();
|
||||
DomainFile df = dataTreeDialog.getDomainFile();
|
||||
|
|
|
@ -92,8 +92,7 @@ class OpenDomainFileTask extends Task {
|
|||
|
||||
private boolean isFileOpen() {
|
||||
List<Archive> dtArchiveList = dtmHandler.getAllArchives();
|
||||
for (int i = 0; i < dtArchiveList.size(); i++) {
|
||||
Archive archive = dtArchiveList.get(i);
|
||||
for (Archive archive : dtArchiveList) {
|
||||
if (archive instanceof ProjectArchive) {
|
||||
ProjectArchive projectArchive = (ProjectArchive) archive;
|
||||
DomainFile archiveDomainFile = projectArchive.getDomainFile();
|
||||
|
@ -156,7 +155,7 @@ class OpenDomainFileTask extends Task {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(tool.getToolFrame(), domainFile.getName(),
|
||||
contentType, "Open", e);
|
||||
contentType, "Open", false, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +178,7 @@ class OpenDomainFileTask extends Task {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
||||
"Open", e);
|
||||
"Open", false, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// we don't care, the task has been canceled
|
||||
|
|
|
@ -77,7 +77,7 @@ public class DataTypeManagerHandler {
|
|||
private Map<UniversalID, InvalidFileArchive> invalidArchives = new HashMap<>();
|
||||
|
||||
private boolean treeDialogCancelled = false;
|
||||
private DomainFileFilter createArchiveFileFilter;
|
||||
private DomainFileFilter archiveFileFilter;
|
||||
|
||||
private DataTypeIndexer dataTypeIndexer;
|
||||
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
|
||||
|
@ -107,18 +107,7 @@ public class DataTypeManagerHandler {
|
|||
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
|
||||
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
|
||||
|
||||
createArchiveFileFilter = new DomainFileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(DomainFile df) {
|
||||
return DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean followLinkedFolders() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
archiveFileFilter = new DefaultDomainFileFilter(DataTypeArchive.class, true);
|
||||
|
||||
folderListener = new MyFolderListener();
|
||||
tool.getProject().getProjectData().addDomainFolderChangeListener(folderListener);
|
||||
|
@ -1454,7 +1443,7 @@ public class DataTypeManagerHandler {
|
|||
}
|
||||
|
||||
private DataTreeDialog getSaveDialog() {
|
||||
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, createArchiveFileFilter);
|
||||
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, archiveFileFilter);
|
||||
|
||||
ActionListener listener = event -> {
|
||||
DomainFolder folder = dialog.getDomainFolder();
|
||||
|
@ -1486,7 +1475,7 @@ public class DataTypeManagerHandler {
|
|||
private CreateDataTypeArchiveDataTreeDialog getCreateDialog() {
|
||||
|
||||
CreateDataTypeArchiveDataTreeDialog dialog = new CreateDataTypeArchiveDataTreeDialog(null,
|
||||
"Create", CREATE, createArchiveFileFilter);
|
||||
"Create", CREATE, archiveFileFilter);
|
||||
|
||||
ActionListener listener = event -> {
|
||||
DomainFolder folder = dialog.getDomainFolder();
|
||||
|
@ -1726,7 +1715,7 @@ public class DataTypeManagerHandler {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(null, newDomainFile.getName(), contentType,
|
||||
"Re-open", e);
|
||||
"Re-open", false, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw new AssertException(e);
|
||||
|
@ -1766,7 +1755,7 @@ public class DataTypeManagerHandler {
|
|||
Throwable cause = t.getCause();
|
||||
if (cause instanceof VersionException) {
|
||||
VersionExceptionHandler.showVersionError(null, archiveFile.getName(), "Archive",
|
||||
"open", (VersionException) cause);
|
||||
"open", false, (VersionException) cause);
|
||||
}
|
||||
else {
|
||||
Msg.showError(plugin, plugin.getProvider().getComponent(), "Open Archive Failed",
|
||||
|
|
|
@ -78,6 +78,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
|
||||
private JTextField filePathTextField;
|
||||
private JButton fileChooserButton;
|
||||
private List<Exporter> applicableExporters;
|
||||
private GhidraComboBox<Exporter> comboBox;
|
||||
private final DomainFile domainFile;
|
||||
private boolean domainObjectWasSupplied;
|
||||
|
@ -86,39 +87,82 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
private PluginTool tool;
|
||||
|
||||
private JLabel selectionOnlyLabel;
|
||||
private boolean showNoExporterErrorIfNeeded = true;
|
||||
|
||||
/**
|
||||
* Construct a new ExporterDialog for exporting an entire program.
|
||||
* Show a new ExporterDialog for exporting an entire program.
|
||||
* The method {@link #hasNoApplicableExporter()} should be checked before showing the
|
||||
* dilaog. If no exporters are available a popup error will be displayed and the exporter
|
||||
* dialog will not be shown.
|
||||
*
|
||||
* @param tool the tool that launched this dialog.
|
||||
* @param domainFile the program to export
|
||||
*/
|
||||
public ExporterDialog(PluginTool tool, DomainFile domainFile) {
|
||||
this(tool, domainFile, null, null);
|
||||
public static void show(PluginTool tool, DomainFile domainFile) {
|
||||
showExporterDialog(tool, domainFile, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new ExporterDialog for exporting a program, optionally only exported a
|
||||
* selected region.
|
||||
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
|
||||
* showing the dilaog. If no exporters are available a popup error will be displayed and the
|
||||
* exporter dialog will not be shown.
|
||||
* The {@link #close()} method must always be invoked on the dialog instance even if it
|
||||
* is never shown to ensure any {@link DomainObject} instance held is properly released.
|
||||
*
|
||||
* @param tool the tool that launched this dialog.
|
||||
* @param domainFile the program file to export. (may be proxy)
|
||||
* @param domainObject the program to export if already open, otherwise null.
|
||||
* @param selection the current program selection (ignored for FrontEnd Tool).
|
||||
*/
|
||||
public ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
|
||||
public static void showExporterDialog(PluginTool tool, DomainFile domainFile,
|
||||
DomainObject domainObject, ProgramSelection selection) {
|
||||
ExporterDialog dialog = new ExporterDialog(tool, domainFile, domainObject, selection);
|
||||
if (dialog.hasNoApplicableExporter()) {
|
||||
dialog.close();
|
||||
}
|
||||
else {
|
||||
tool.showDialog(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new modal ExporterDialog for exporting a program, optionally only exported a
|
||||
* selected region. The method {@link #hasNoApplicableExporter()} should be checked before
|
||||
* showing the dilaog. If no exporters are available a popup error will be displayed.
|
||||
* The {@link #close()} method must always be invoked on the dialog instance even if it
|
||||
* is never shown to ensure any {@link DomainObject} instance held is properly released.
|
||||
*
|
||||
* @param tool the tool that launched this dialog.
|
||||
* @param domainFile the program file to export. (may be proxy)
|
||||
* @param domainObject the program to export if already open, otherwise null.
|
||||
* @param selection the current program selection (ignored for FrontEnd Tool).
|
||||
*/
|
||||
private ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
|
||||
ProgramSelection selection) {
|
||||
super("Export " + domainFile.getName());
|
||||
|
||||
if (!Swing.isSwingThread()) {
|
||||
throw new RuntimeException("ExporterDialog must be instantiated within Swing thread");
|
||||
}
|
||||
|
||||
this.tool = tool;
|
||||
this.domainFile = domainFile;
|
||||
this.domainObject = domainObject;
|
||||
this.currentSelection = selection;
|
||||
|
||||
if (domainObject != null) {
|
||||
applicableExporters = getApplicableExporters(false);
|
||||
domainObjectWasSupplied = true;
|
||||
domainObject.addConsumer(this);
|
||||
}
|
||||
else {
|
||||
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
|
||||
applicableExporters = getApplicableExporters(true);
|
||||
List<Exporter> applicableDomainFileExporters = getApplicableExporters(false);
|
||||
|
||||
domainObject = getDomainObjectIfNeeded(!applicableDomainFileExporters.isEmpty());
|
||||
|
||||
applicableExporters = getApplicableExporters(false);
|
||||
}
|
||||
|
||||
addWorkPanel(buildWorkPanel());
|
||||
|
@ -133,6 +177,11 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
// need to initialize a few things
|
||||
selectedFormatChanged();
|
||||
validate();
|
||||
|
||||
if (showNoExporterErrorIfNeeded && hasNoApplicableExporter()) {
|
||||
Msg.showError(this, tool.getToolFrame(), "Unable to Export",
|
||||
"No available exporters for content type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -305,10 +354,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
|
||||
private Component buildFormatChooser() {
|
||||
|
||||
List<Exporter> exporters = getApplicableExporters(false);
|
||||
comboBox = new GhidraComboBox<>(exporters);
|
||||
comboBox = new GhidraComboBox<>(applicableExporters);
|
||||
|
||||
Exporter defaultExporter = getDefaultExporter(exporters);
|
||||
Exporter defaultExporter = getDefaultExporter(applicableExporters);
|
||||
if (defaultExporter != null) {
|
||||
comboBox.setSelectedItem(defaultExporter);
|
||||
}
|
||||
|
@ -319,8 +367,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
|
||||
/**
|
||||
* This list generation will be based upon the open domainObject if available, otherwise
|
||||
* the domainFile's content class will be used.
|
||||
* @return list of exporters able to handle content
|
||||
* the domainFile's content class will be used. The {@code applicableExporters} variable
|
||||
* is set to the applicable list of exporters.
|
||||
*/
|
||||
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
|
||||
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
|
||||
|
@ -330,16 +378,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
}
|
||||
|
||||
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
|
||||
if (exporter.canExportDomainFile(domainFile)) {
|
||||
return true;
|
||||
}
|
||||
if (domainObject == null) {
|
||||
return preliminaryCheck
|
||||
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
|
||||
: false;
|
||||
}
|
||||
if (domainObject != null) {
|
||||
return exporter.canExportDomainObject(domainObject);
|
||||
}
|
||||
if (preliminaryCheck) {
|
||||
return exporter.canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
}
|
||||
return exporter.canExportDomainFile(domainFile);
|
||||
}
|
||||
|
||||
private Exporter getDefaultExporter(List<Exporter> list) {
|
||||
|
||||
|
@ -410,6 +456,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
setOkEnabled(true);
|
||||
}
|
||||
|
||||
public boolean hasNoApplicableExporter() {
|
||||
return applicableExporters.isEmpty();
|
||||
}
|
||||
|
||||
private boolean hasOptions() {
|
||||
return options != null && !options.isEmpty();
|
||||
}
|
||||
|
@ -450,7 +500,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
}
|
||||
}
|
||||
|
||||
private DomainObject getDomainObjectIfNeeded(TaskMonitor taskMonitor) {
|
||||
private DomainObject getDomainObjectIfNeeded(boolean exportPossibleWithoutOpening) {
|
||||
if (domainObject != null) {
|
||||
return domainObject;
|
||||
}
|
||||
|
@ -459,7 +509,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
// direct domain file export. This avoids potential upgrade issues and preserves
|
||||
// database in its current state for those exporters.
|
||||
boolean doOpen = false;
|
||||
for (Exporter exporter : getApplicableExporters(true)) {
|
||||
for (Exporter exporter : applicableExporters) {
|
||||
if (!exporter.canExportDomainFile(domainFile)) {
|
||||
doOpen = true;
|
||||
break;
|
||||
|
@ -469,35 +519,28 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
return null;
|
||||
}
|
||||
|
||||
if (SystemUtilities.isEventDispatchThread()) {
|
||||
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
|
||||
monitor -> doOpenFile(monitor));
|
||||
}
|
||||
else {
|
||||
doOpenFile(taskMonitor);
|
||||
}
|
||||
monitor -> doOpenFile(exportPossibleWithoutOpening, monitor));
|
||||
|
||||
return domainObject;
|
||||
}
|
||||
|
||||
private void doOpenFile(TaskMonitor monitor) {
|
||||
private void doOpenFile(boolean exportPossibleWithoutOpening, TaskMonitor monitor) {
|
||||
showNoExporterErrorIfNeeded = false;
|
||||
String linkedPrefix = domainFile.isLink() ? "linked-" : "";
|
||||
try {
|
||||
if (domainFile.isLinkFile()) {
|
||||
// Linked files are always subject to upgrade if needed and do not support
|
||||
// getImmutableDomainObject
|
||||
domainObject =
|
||||
domainFile.getReadOnlyDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
||||
}
|
||||
else {
|
||||
domainObject =
|
||||
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
|
||||
}
|
||||
}
|
||||
catch (VersionException e) {
|
||||
String msg = "Could not open file: " + domainFile.getName() +
|
||||
"\n\nAvailable export options will be limited.";
|
||||
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
|
||||
if (exportPossibleWithoutOpening) {
|
||||
msg += "\n\nAvailable export options will be limited.";
|
||||
}
|
||||
if (e.isUpgradable()) {
|
||||
msg += "\n\nA data upgrade is required. You may open file" +
|
||||
"\nin a tool first then Export if a different exporter" + "\nis required.";
|
||||
msg += "\n\nA " + linkedPrefix +
|
||||
"content upgrade is required. You may open file in a" +
|
||||
"\ntool first to complete upgrade then Export if needed.";
|
||||
}
|
||||
else {
|
||||
msg += "\nFile was created with a newer version of Ghidra";
|
||||
|
@ -505,8 +548,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
Msg.showError(this, getComponent(), "Error Opening File", msg);
|
||||
}
|
||||
catch (IOException e) {
|
||||
String msg = "Could not open file: " + domainFile.getName() +
|
||||
"\n\nAvailable export options will be limited.";
|
||||
String msg = "Could not open " + linkedPrefix + "file: " + domainFile.getName();
|
||||
if (exportPossibleWithoutOpening) {
|
||||
msg += "\n\nAvailable export options will be limited.";
|
||||
}
|
||||
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
|
@ -552,7 +597,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
|
|||
|
||||
boolean exportDomainFile =
|
||||
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
|
||||
if (!exportDomainFile && domainFile == null) {
|
||||
if (!exportDomainFile && (domainFile == null || domainFile.isLink())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -70,9 +70,8 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||
protected void actionPerformed(NavigatableActionContext context) {
|
||||
Program program = context.getProgram();
|
||||
DomainFile domainFile = program.getDomainFile();
|
||||
ExporterDialog dialog =
|
||||
new ExporterDialog(tool, domainFile, program, context.getSelection());
|
||||
tool.showDialog(dialog);
|
||||
ExporterDialog.showExporterDialog(tool, domainFile, program,
|
||||
context.getSelection());
|
||||
}
|
||||
};
|
||||
MenuData menuData =
|
||||
|
@ -104,8 +103,7 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||
@Override
|
||||
protected void actionPerformed(ProjectDataContext context) {
|
||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||
ExporterDialog dialog = new ExporterDialog(tool, domainFile);
|
||||
tool.showDialog(dialog);
|
||||
ExporterDialog.show(tool, domainFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -118,6 +116,10 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
|
|||
if (selectedFiles.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||
if (domainFile.isLink() && domainFile.getLinkInfo().isFolderLink()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.gotoquery;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -32,7 +30,7 @@ import ghidra.app.util.NamespaceUtils;
|
|||
import ghidra.app.util.SymbolPath;
|
||||
import ghidra.app.util.query.TableService;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.ProgramFileChooser;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
|
@ -106,8 +104,10 @@ public class GoToHelper {
|
|||
ExternalLocation externalLoc =
|
||||
program.getExternalManager().getExternalLocation(externalSym);
|
||||
|
||||
// TODO - this seems like a mistake to always pass 'false' here; please doc why we
|
||||
// wish to ignore the user options for when to navigate to external programs
|
||||
// TODO - This seems like a mistake to always pass 'false' here; please doc why we
|
||||
// wish to ignore the user options for when to navigate to external programs.
|
||||
// It appears this was done since this method is invoked on simple external
|
||||
// location node selection within symbol tree where you would not want a popup.
|
||||
return goToExternalLinkage(navigatable, externalLoc, false);
|
||||
}
|
||||
|
||||
|
@ -187,10 +187,11 @@ public class GoToHelper {
|
|||
*
|
||||
* @param nav Navigatable
|
||||
* @param externalLoc external location
|
||||
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist,
|
||||
* otherwise navigation to the first linkage location will be performed
|
||||
* @param popupAllowed if true a table may be displayed when multiple linkage locations exist
|
||||
* or navigation to an external program, otherwise navigation to the first linkage
|
||||
* location will be performed
|
||||
* @return true if navigation was successful or a list of possible linkage locations was
|
||||
* displayed.
|
||||
* displayed, false if no navigation was performed.
|
||||
*/
|
||||
protected boolean goToExternalLinkage(Navigatable nav, ExternalLocation externalLoc,
|
||||
boolean popupAllowed) {
|
||||
|
@ -205,8 +206,14 @@ public class GoToHelper {
|
|||
NavigationUtils.getExternalLinkageAddresses(program, externalSym.getAddress());
|
||||
if (externalLinkageAddresses.length == 0) {
|
||||
if (externalLoc.isFunction()) {
|
||||
tool.setStatusInfo("Failed to identify external linkage address for " +
|
||||
externalSym.getName(true) + ". Unable to perform navigation.", true);
|
||||
// This assume external functions always require linkage location
|
||||
tool.setStatusInfo("Failed to identify external linkage address for function " +
|
||||
externalSym.getName(true), true);
|
||||
}
|
||||
else if (popupAllowed) {
|
||||
// If there are no linkage location try to navigate to external program if a popup
|
||||
// is tolerated.
|
||||
return goToExternalLocation(nav, externalLoc, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -306,7 +313,7 @@ public class GoToHelper {
|
|||
}
|
||||
|
||||
ProjectData pd = tool.getProject().getProjectData();
|
||||
DomainFile domainFile = pd.getFile(pathName);
|
||||
DomainFile domainFile = pd.getFile(pathName, ProgramFileChooser.PROGRAM_FILE_FILTER);
|
||||
ProgramManager service = tool.getService(ProgramManager.class);
|
||||
if (domainFile == null || service == null) {
|
||||
tool.setStatusInfo("Unable to navigate to external location. " +
|
||||
|
@ -441,8 +448,8 @@ public class GoToHelper {
|
|||
return;
|
||||
}
|
||||
|
||||
DataTreeDialog dialog = new DataTreeDialog(null,
|
||||
"Choose External Program (" + extProgName + ")", OPEN);
|
||||
ProgramFileChooser dialog =
|
||||
new ProgramFileChooser(null, "Choose External Program (" + extProgName + ")");
|
||||
dialog.setSearchText(extProgName);
|
||||
dialog.setHelpLocation(new HelpLocation("ReferencesPlugin", "ChooseExternalProgram"));
|
||||
tool.showDialog(dialog);
|
||||
|
|
|
@ -79,16 +79,25 @@ public class AboutProgramPlugin extends Plugin implements ApplicationLevelPlugin
|
|||
@Override
|
||||
protected void actionPerformed(ProjectDataContext context) {
|
||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||
showAbout(domainFile, domainFile.getMetadata());
|
||||
Map<String, String> metadata = domainFile.getMetadata();
|
||||
|
||||
showAbout(domainFile, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAddToPopup(ProjectDataContext context) {
|
||||
return context.getFileCount() == 1 && context.getFolderCount() == 0;
|
||||
if (context.getFileCount() == 1 && context.getFolderCount() == 0) {
|
||||
// Adjust popup menu text
|
||||
DomainFile domainFile = context.getSelectedFiles().get(0);
|
||||
String contentType = domainFile.getContentType();
|
||||
setPopupMenuData(
|
||||
new MenuData(new String[] { "About " + contentType }, null, "AAA"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
aboutAction.setPopupMenuData(
|
||||
new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
|
||||
aboutAction.setPopupMenuData(new MenuData(new String[] { ACTION_NAME }, null, "AAA"));
|
||||
|
||||
aboutAction.setEnabled(true);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,10 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
|
|||
return false;
|
||||
}
|
||||
|
||||
if (file.isLink() && file.getLinkInfo().isExternalLink()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file.isInWritableProject() &&
|
||||
Program.class.isAssignableFrom(file.getDomainObjectClass());
|
||||
}
|
||||
|
@ -105,12 +109,12 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
|
|||
}
|
||||
|
||||
};
|
||||
setLanguageAction.setPopupMenuData(
|
||||
new MenuData(new String[] { "Set Language..." }, "Language"));
|
||||
setLanguageAction
|
||||
.setPopupMenuData(new MenuData(new String[] { "Set Language..." }, "Language"));
|
||||
|
||||
setLanguageAction.setEnabled(true);
|
||||
setLanguageAction.setHelpLocation(
|
||||
new HelpLocation("LanguageProviderPlugin", "set language"));
|
||||
setLanguageAction
|
||||
.setHelpLocation(new HelpLocation("LanguageProviderPlugin", "set language"));
|
||||
tool.addAction(setLanguageAction);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,10 @@ import java.net.URL;
|
|||
import java.util.Objects;
|
||||
|
||||
import ghidra.framework.data.DomainFileProxy;
|
||||
import ghidra.framework.data.LinkHandler;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Programs locations can be specified from either a {@link DomainFile} or a ghidra {@link URL}.
|
||||
|
@ -81,11 +81,19 @@ public class ProgramLocator {
|
|||
file = domainFile;
|
||||
}
|
||||
else {
|
||||
if (domainFile instanceof LinkedDomainFile linkedFile) {
|
||||
try {
|
||||
url = GhidraURL.getNormalizedURL(resolveURL(domainFile));
|
||||
// Attempt to resolve to actual linked-file to allow for
|
||||
// direct URL reference
|
||||
domainFile = linkedFile.getLinkedFile();
|
||||
}
|
||||
catch (IOException e) {
|
||||
file = domainFile;
|
||||
Msg.error(this, "Failed to resolve linked-file", e);
|
||||
}
|
||||
}
|
||||
url = domainFile.getLocalProjectURL(null);
|
||||
if (url == null) {
|
||||
url = domainFile.getSharedProjectURL(null);
|
||||
}
|
||||
}
|
||||
this.domainFile = file;
|
||||
|
@ -177,25 +185,4 @@ public class ProgramLocator {
|
|||
Objects.equals(ghidraURL, other.ghidraURL) && version == other.version;
|
||||
}
|
||||
|
||||
private URL resolveURL(DomainFile file) throws IOException {
|
||||
if (file.isLinkFile()) {
|
||||
return LinkHandler.getURL(file);
|
||||
}
|
||||
DomainFolder parent = file.getParent();
|
||||
if (file instanceof LinkedDomainFile linkedFile) {
|
||||
return resolveLinkedDomainFile(linkedFile);
|
||||
}
|
||||
if (!parent.getProjectLocator().isTransient()) {
|
||||
return file.getLocalProjectURL(null);
|
||||
}
|
||||
return file.getSharedProjectURL(null);
|
||||
}
|
||||
|
||||
private URL resolveLinkedDomainFile(LinkedDomainFile linkedFile) {
|
||||
URL url = linkedFile.getLocalProjectURL(null);
|
||||
if (url == null) {
|
||||
url = linkedFile.getSharedProjectURL(null);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,23 +41,12 @@ class ProgramSaveManager {
|
|||
private ProgramManager programMgr;
|
||||
private PluginTool tool;
|
||||
private boolean treeDialogCancelled;
|
||||
private DomainFileFilter domainFileFilter;
|
||||
private DomainFileFilter programFileFilter;
|
||||
|
||||
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
|
||||
this.tool = tool;
|
||||
this.programMgr = programMgr;
|
||||
domainFileFilter = new DomainFileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(DomainFile df) {
|
||||
return Program.class.isAssignableFrom(df.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean followLinkedFolders() {
|
||||
return false; // can't save to linked-folder (read-only)
|
||||
}
|
||||
};
|
||||
programFileFilter = new DefaultDomainFileFilter(Program.class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -443,8 +432,7 @@ class ProgramSaveManager {
|
|||
}
|
||||
|
||||
private DataTreeDialog getSaveDialog() {
|
||||
DataTreeDialog dialog =
|
||||
new DataTreeDialog(null, "Save As", SAVE, domainFileFilter);
|
||||
DataTreeDialog dialog = new DataTreeDialog(null, "Save As", SAVE, programFileFilter);
|
||||
|
||||
ActionListener listener = event -> {
|
||||
DomainFolder folder = dialog.getDomainFolder();
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.references;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.*;
|
||||
|
@ -30,7 +28,7 @@ import javax.swing.event.DocumentListener;
|
|||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import docking.widgets.label.GLabel;
|
||||
import ghidra.app.util.AddressInput;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.ProgramFileChooser;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -113,8 +111,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
|||
}
|
||||
});
|
||||
|
||||
editButton = new JButton("Edit");
|
||||
editButton.setToolTipText("Edit Link to External Program");
|
||||
editButton = new JButton("Select...");
|
||||
editButton.setToolTipText("Select External Program");
|
||||
editButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
|
@ -187,10 +185,8 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
|||
* Pop up the data tree dialog so the user can choose the external program.
|
||||
*/
|
||||
private void popupProgramChooser() {
|
||||
DataTreeDialog d =
|
||||
new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
|
||||
final DataTreeDialog dialog = d;
|
||||
d.addOkActionListener(new ActionListener() {
|
||||
ProgramFileChooser dialog = new ProgramFileChooser(this.getParent(), "Choose External Program");
|
||||
dialog.addOkActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
DomainFile df = dialog.getDomainFile();
|
||||
|
@ -206,7 +202,7 @@ class EditExternalReferencePanel extends EditReferencePanel {
|
|||
extLibPath.setText(df.getPathname());
|
||||
}
|
||||
});
|
||||
plugin.getTool().showDialog(d);
|
||||
plugin.getTool().showDialog(dialog);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.references;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.*;
|
||||
|
@ -34,7 +32,7 @@ import generic.theme.GIcon;
|
|||
import ghidra.app.cmd.refs.*;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.cmd.CompoundCmd;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.*;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainObjectListener;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
|
@ -236,8 +234,8 @@ public class ExternalReferencesProvider extends ComponentProviderAdapter {
|
|||
private void setExternalProgramAssociation() {
|
||||
List<String> selectedExternalNames = getSelectedExternalNames();
|
||||
String externalName = selectedExternalNames.get(0); // must be exactly one for us to be enabled.
|
||||
DataTreeDialog dialog = new DataTreeDialog(mainPanel,
|
||||
"Choose External Program (" + externalName + ")", OPEN);
|
||||
DataTreeDialog dialog = new ProgramFileChooser(mainPanel,
|
||||
"Choose External Program (" + externalName + ")", AppInfo.getActiveProject());
|
||||
|
||||
dialog.setSearchText(externalName);
|
||||
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.symboltree;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.Arrays;
|
||||
|
@ -36,7 +34,7 @@ import docking.widgets.label.GLabel;
|
|||
import ghidra.app.util.AddressInput;
|
||||
import ghidra.app.util.NamespaceUtils;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.ProgramFileChooser;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
|
@ -270,9 +268,9 @@ class EditExternalLocationPanel extends JPanel {
|
|||
* Pop up the data tree dialog so the user can choose the external program.
|
||||
*/
|
||||
private void popupProgramChooser() {
|
||||
DataTreeDialog d = new DataTreeDialog(this.getParent(), "Choose External Program", OPEN);
|
||||
final DataTreeDialog dialog = d;
|
||||
d.addOkActionListener(e -> {
|
||||
ProgramFileChooser dialog =
|
||||
new ProgramFileChooser(this.getParent(), "Choose External Program");
|
||||
dialog.addOkActionListener(e -> {
|
||||
DomainFile df = dialog.getDomainFile();
|
||||
if (df == null) {
|
||||
return;
|
||||
|
@ -285,7 +283,7 @@ class EditExternalLocationPanel extends JPanel {
|
|||
dialog.close();
|
||||
extLibPathTextField.setText(df.getPathname());
|
||||
});
|
||||
DockingWindowManager.showDialog(this, d);
|
||||
DockingWindowManager.showDialog(this, dialog);
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
|
@ -363,7 +361,8 @@ class EditExternalLocationPanel extends JPanel {
|
|||
|
||||
Project project = AppInfo.getActiveProject();
|
||||
ProjectData projectData = project.getProjectData();
|
||||
DomainFile file = projectData.getFile(extLibPath);
|
||||
DomainFile file =
|
||||
projectData.getFile(extLibPath, ProgramFileChooser.PROGRAM_FILE_FILTER);
|
||||
if (file == null) {
|
||||
showInputErr("Cannot find the program for the specified library 'Path' of " +
|
||||
extLibPath + ".");
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.symboltree.actions;
|
||||
|
||||
import static ghidra.framework.main.DataTreeDialogType.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
@ -29,7 +27,7 @@ import ghidra.app.plugin.core.symboltree.*;
|
|||
import ghidra.app.plugin.core.symboltree.nodes.LibrarySymbolNode;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.main.ProgramFileChooser;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.ExternalManager;
|
||||
|
@ -82,8 +80,8 @@ public class SetExternalProgramAction extends SymbolTreeContextAction {
|
|||
ExternalManager externalManager = program.getExternalManager();
|
||||
final String externalLibraryPath = externalManager.getExternalLibraryPath(externalName);
|
||||
|
||||
final DataTreeDialog dialog = new DataTreeDialog(provider.getComponent(),
|
||||
"Choose External Program (" + externalName + ")", OPEN);
|
||||
ProgramFileChooser dialog = new ProgramFileChooser(provider.getComponent(),
|
||||
"Choose External Program (" + externalName + ")");
|
||||
|
||||
dialog.setSearchText(externalName);
|
||||
|
||||
|
|
|
@ -2827,8 +2827,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
|||
DomainFile choice = loadAskValue(this::parseDomainFile, title);
|
||||
if (!isRunningHeadless()) {
|
||||
choice = doAsk(Program.class, title, "", choice, lastValue -> {
|
||||
|
||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
||||
// File filter employed limits access to program files within the active project
|
||||
// only to ensure the ability to open for update is possible.
|
||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
|
||||
new DefaultDomainFileFilter(Program.class, true));
|
||||
dtd.show();
|
||||
if (dtd.wasCancelled()) {
|
||||
return null;
|
||||
|
@ -2932,8 +2934,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
|||
|
||||
String message = "";
|
||||
DomainFile choice = doAsk(DomainFile.class, title, message, existingValue, lastValue -> {
|
||||
|
||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN);
|
||||
// File filter employed limits access to files within the active project
|
||||
// only to ensure the ability to open for update is possible.
|
||||
DataTreeDialog dtd = new DataTreeDialog(null, title, OPEN,
|
||||
DomainFileFilter.ALL_FILES_NO_EXTERNAL_FOLDERS_FILTER);
|
||||
dtd.show();
|
||||
if (dtd.wasCancelled()) {
|
||||
throw new CancelledException();
|
||||
|
|
|
@ -47,7 +47,8 @@ public class GdtExporter extends Exporter {
|
|||
|
||||
@Override
|
||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
// Avoid exporting link-file itself
|
||||
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,7 +42,8 @@ public class GzfExporter extends Exporter {
|
|||
|
||||
@Override
|
||||
public boolean canExportDomainFile(DomainFile domainFile) {
|
||||
return canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
// Avoid exporting link-file itself
|
||||
return !domainFile.isLink() && canExportDomainObject(domainFile.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,6 +43,7 @@ import ghidra.framework.model.*;
|
|||
import ghidra.framework.project.DefaultProject;
|
||||
import ghidra.framework.project.DefaultProjectManager;
|
||||
import ghidra.framework.protocol.ghidra.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
|
@ -354,7 +355,10 @@ public class HeadlessAnalyzer {
|
|||
}
|
||||
throw new IOException(title + ": " + message);
|
||||
}
|
||||
}, TaskMonitor.DUMMY);
|
||||
|
||||
// Link files are skipped to avoid duplicate processing
|
||||
// Processing should be done on actual folder - not a linked folder
|
||||
}, LinkFileControl.NO_FOLLOW, TaskMonitor.DUMMY);
|
||||
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
|
@ -1338,7 +1342,7 @@ public class HeadlessAnalyzer {
|
|||
boolean filesProcessed = false;
|
||||
|
||||
DomainFile domFile = parentFolder.getFile(filename);
|
||||
// Do not follow folder-links or consider program links. Using content type
|
||||
// Do not follow folder-links or program links. Using content type
|
||||
// to filter is best way to control this.
|
||||
if (domFile != null &&
|
||||
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import ghidra.framework.client.RepositoryAdapter;
|
|||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLQuery;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLQuery.LinkFileControl;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLResultHandlerAdapter;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.ExclusiveCheckoutException;
|
||||
|
@ -103,13 +104,13 @@ public class ProgramOpener {
|
|||
|
||||
AtomicReference<Program> openedProgram = new AtomicReference<>();
|
||||
try {
|
||||
GhidraURLQuery.queryUrl(ghidraUrl, new GhidraURLResultHandlerAdapter() {
|
||||
GhidraURLQuery.queryUrl(ghidraUrl, Program.class, new GhidraURLResultHandlerAdapter() {
|
||||
@Override
|
||||
public void processResult(DomainFile domainFile, URL url, TaskMonitor m) {
|
||||
Program p = openProgram(locator, domainFile, m); // may return null
|
||||
openedProgram.set(p);
|
||||
}
|
||||
}, monitor);
|
||||
}, LinkFileControl.FOLLOW_EXTERNAL, monitor);
|
||||
}
|
||||
catch (IOException | CancelledException e) {
|
||||
// IOException reported to user by GhidraURLResultHandlerAdapter
|
||||
|
@ -148,7 +149,7 @@ public class ProgramOpener {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
String contentType = domainFile.getContentType();
|
||||
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", e);
|
||||
VersionExceptionHandler.showVersionError(null, filename, contentType, "Open", false, e);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// we don't care, the task has been cancelled
|
||||
|
@ -197,7 +198,7 @@ public class ProgramOpener {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
|
||||
"Open", e);
|
||||
"Open", false, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -129,31 +129,37 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
|||
serviceProvider.getService(ProjectDataService.class);
|
||||
ProjectData projectData = projectDataService.getProjectData();
|
||||
|
||||
// default folder is the root folder
|
||||
DomainFolder folder = projectData.getRootFolder();
|
||||
|
||||
// Get program name and folder from program comment annotation
|
||||
// handles forward and back slashes and with and without first slash
|
||||
String programText = getProgramText(annotationParts);
|
||||
String programName = FilenameUtils.getName(programText);
|
||||
String path = FilenameUtils.getFullPathNoEndSeparator(programText);
|
||||
|
||||
DomainFolder folder;
|
||||
if (path.length() > 0) {
|
||||
path = StringUtils.prependIfMissing(FilenameUtils.separatorsToUnix(path), "/");
|
||||
folder = projectData.getFolder(path);
|
||||
}
|
||||
|
||||
if (folder == null) {
|
||||
Msg.showInfo(getClass(), null, "No Folder: " + path,
|
||||
Msg.showInfo(getClass(), null, "Folder Not Found: " + path,
|
||||
"Unable to locate folder by the name \"" + path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
folder = projectData.getRootFolder();
|
||||
}
|
||||
|
||||
DomainFile programFile = findProgramByName(programName, folder);
|
||||
|
||||
if (programFile == null) {
|
||||
Msg.showInfo(getClass(), null, "No Program: " + programName,
|
||||
"Unable to locate a program by the name \"" + programName +
|
||||
"\".\nNOTE: Program name is case-sensitive. ");
|
||||
DomainFile programFile = folder.getFile(programName);
|
||||
if (programFile == null ||
|
||||
!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
|
||||
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
|
||||
"Unable to locate program at path \"" + programText +
|
||||
"\".\nNOTE: File names are case-sensitive.");
|
||||
return true;
|
||||
}
|
||||
if (!Program.class.isAssignableFrom(programFile.getDomainObjectClass())) {
|
||||
Msg.showInfo(getClass(), null, "Program Not Found: " + programName,
|
||||
"File exists with incorrect content type. ");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -199,7 +205,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
Msg.showInfo(getClass(), null, "No Symbol: " + symbolName,
|
||||
Msg.showInfo(getClass(), null, "Symbol Not Found: " + symbolName,
|
||||
"Unable to navigate to '" + symbolName + "' in the program '" + programFile.getName() +
|
||||
"'.\nMake sure that the given symbol/address exists.");
|
||||
if (!programManager.isVisible(program)) {
|
||||
|
@ -247,27 +253,6 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
|||
return address;
|
||||
}
|
||||
|
||||
// recursive program to find a program by the given name within the given folder
|
||||
private DomainFile findProgramByName(String programText, DomainFolder folder) {
|
||||
DomainFile[] files = folder.getFiles();
|
||||
for (DomainFile file : files) {
|
||||
if (file.getName().equals(programText)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
// not at the current level, then check sub-folders
|
||||
DomainFolder[] folders = folder.getFolders();
|
||||
for (DomainFolder subFolder : folders) {
|
||||
DomainFile domainFile = findProgramByName(programText, subFolder);
|
||||
if (domainFile != null) {
|
||||
return domainFile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "Program";
|
||||
|
@ -275,7 +260,7 @@ public class ProgramAnnotatedStringHandler implements AnnotatedStringHandler {
|
|||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@program program_name.exe@symbol_name}";
|
||||
return "{@program program_path@symbol_name}";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -291,16 +291,28 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
else {
|
||||
domainFile = treePanel.getSelectedDomainFile();
|
||||
if (domainFile != null) {
|
||||
LinkFileInfo linkInfo = domainFile.getLinkInfo();
|
||||
if (linkInfo != null && linkInfo.isFolderLink()) {
|
||||
// Ensure we don't have a folder name conflict
|
||||
if (domainFile.getParent().getFolder(domainFile.getName()) == null) {
|
||||
domainFolder = linkInfo.getLinkedFolder();
|
||||
domainFile = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
folderNameLabel.setText(domainFile.getParent().getPathname());
|
||||
nameField.setText(domainFile.getName());
|
||||
domainFolder = domainFile.getParent();
|
||||
}
|
||||
else {
|
||||
}
|
||||
|
||||
if (domainFile == null) {
|
||||
if (domainFolder == null) {
|
||||
domainFolder = treePanel.getSelectedDomainFolder();
|
||||
if (domainFolder == null) {
|
||||
domainFolder = project.getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
}
|
||||
folderNameLabel.setText(domainFolder.getPathname());
|
||||
if (nameField.isEditable()) {
|
||||
if (nameField.getText().length() > 0) {
|
||||
|
@ -349,8 +361,10 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
* @param file the file
|
||||
*/
|
||||
public void selectDomainFile(DomainFile file) {
|
||||
if (file != null) {
|
||||
Swing.runLater(() -> treePanel.selectDomainFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
@ -584,20 +598,6 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
}
|
||||
}
|
||||
|
||||
protected static DomainFileFilter getDefaultFilter(DataTreeDialogType type) {
|
||||
if (type == CHOOSE_FOLDER || type == OPEN) {
|
||||
// return filter which forces folder selection and allow navigation into linked-folders
|
||||
return new DomainFileFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(DomainFile df) {
|
||||
return true; // show all files (legacy behavior)
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private class FieldKeyListener extends KeyAdapter {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
|
|
|
@ -17,8 +17,7 @@ package ghidra.framework.main;
|
|||
|
||||
import java.awt.Component;
|
||||
|
||||
import ghidra.framework.model.DomainFileFilter;
|
||||
import ghidra.framework.model.Project;
|
||||
import ghidra.framework.model.*;
|
||||
|
||||
/**
|
||||
* Dialog to open or save domain data items to a new location or name.
|
||||
|
@ -33,9 +32,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
|||
|
||||
/**
|
||||
* Construct a new DataTreeDialog for the active project. This chooser will show all project
|
||||
* files. Following linked-folders will only be allowed if a type of CHOOSE_FOLDER
|
||||
* or OPEN is specified. If different behavior is required a filter should
|
||||
* be specified using the other constructor.
|
||||
* files and/or folders within the active project only. Broken and external links will not be
|
||||
* shown. If different behavior is required a filter should be specified using the other
|
||||
* constructor.
|
||||
*
|
||||
* @param parent dialog's parent
|
||||
* @param title title to use
|
||||
|
@ -43,7 +42,7 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
|||
* @throws IllegalArgumentException if invalid type is specified
|
||||
*/
|
||||
public DataTreeDialog(Component parent, String title, DataTreeDialogType type) {
|
||||
this(parent, title, type, getDefaultFilter(type));
|
||||
this(parent, title, type, DomainFileFilter.ALL_INTERNAL_FILES_FILTER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,7 +51,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
|||
* @param parent dialog's parent
|
||||
* @param title title to use
|
||||
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
||||
* @param filter filter used to control what is displayed in the data tree
|
||||
* @param filter filter used to control what is displayed in the data tree. See static
|
||||
* implementations provided by {@link DomainFileFilter} and a more tailored
|
||||
* {@link DefaultDomainFileFilter}.
|
||||
* @throws IllegalArgumentException if invalid type is specified
|
||||
*/
|
||||
public DataTreeDialog(Component parent, String title, DataTreeDialogType type,
|
||||
|
@ -66,7 +67,9 @@ public class DataTreeDialog extends AbstractDataTreeDialog {
|
|||
* @param parent dialog's parent
|
||||
* @param title title to use
|
||||
* @param type specify OPEN, SAVE, CHOOSE_FOLDER, or CREATE
|
||||
* @param filter filter used to control what is displayed in the data tree
|
||||
* @param filter filter used to control what is displayed in the data tree. See static
|
||||
* implementations provided by {@link DomainFileFilter} and a more tailored
|
||||
* {@link DefaultDomainFileFilter}.
|
||||
* @param project the project to browse
|
||||
* @throws IllegalArgumentException if invalid type is specified
|
||||
*/
|
||||
|
|
|
@ -90,9 +90,8 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
|||
*/
|
||||
public OpenVersionedFileDialog(PluginTool tool, String title, Class<T> domainObjectClass,
|
||||
List<T> openDomainObjects) {
|
||||
super(tool.getToolFrame(), title, OPEN, f -> {
|
||||
return domainObjectClass.isAssignableFrom(f.getDomainObjectClass());
|
||||
}, AppInfo.getActiveProject());
|
||||
super(tool.getToolFrame(), title, OPEN,
|
||||
new DefaultDomainFileFilter(domainObjectClass, false), AppInfo.getActiveProject());
|
||||
|
||||
this.tool = tool;
|
||||
this.domainObjectClass = domainObjectClass;
|
||||
|
@ -214,8 +213,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
|||
tabbedPane = new JTabbedPane();
|
||||
tabbedPane.setName("Tabs");
|
||||
tabbedPane.add("Project Files", projectFilePanel);
|
||||
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s",
|
||||
buildOpenObjectsTable());
|
||||
tabbedPane.add("Open " + domainObjectClass.getSimpleName() + "s", buildOpenObjectsTable());
|
||||
|
||||
tabbedPane.addChangeListener(e -> {
|
||||
int selectedTabIndex = tabbedPane.getModel().getSelectedIndex();
|
||||
|
@ -254,8 +252,7 @@ public class OpenVersionedFileDialog<T extends DomainObject> extends AbstractDat
|
|||
|
||||
openObjectsTable = new GFilterTable<>(new OpenObjectsTableModel());
|
||||
GTable table = openObjectsTable.getTable();
|
||||
table.getSelectionModel()
|
||||
.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
openObjectsTable.addSelectionListener(e -> {
|
||||
setOkEnabled(true);
|
||||
okButton.setToolTipText("Use the selected " + domainObjectClass.getSimpleName());
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.main;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* {@link ProgramFileChooser} facilitates selection of an existing project Program file including
|
||||
* Program link-files which may link to either internal or external program files.
|
||||
* This chooser operates in the {@link DataTreeDialogType#OPEN open mode} for selecting
|
||||
* an existing file only.
|
||||
* <P>
|
||||
* This chooser should not be used to facilitate an immediate or
|
||||
* future save-as operation or to open a Program for update since it can return a read-only file.
|
||||
* A more taylored {@link DataTreeDialog} should be used for case where the file will be written.
|
||||
*/
|
||||
public class ProgramFileChooser extends DataTreeDialog {
|
||||
|
||||
/**
|
||||
* This file filter permits selection of any program including those than can be
|
||||
* found by following bother internal and external folder and files links.
|
||||
*/
|
||||
public static final DomainFileFilter PROGRAM_FILE_FILTER =
|
||||
new DefaultDomainFileFilter(Program.class, false);
|
||||
|
||||
/**
|
||||
* Construct a new ProgramChooser for the active project.
|
||||
*
|
||||
* @param parent dialog's parent
|
||||
* @param title title to use
|
||||
* @throws IllegalArgumentException if invalid type is specified
|
||||
*/
|
||||
public ProgramFileChooser(Component parent, String title) {
|
||||
super(parent, title, DataTreeDialogType.OPEN, PROGRAM_FILE_FILTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new DataTreeDialog for the given project.
|
||||
*
|
||||
* @param parent dialog's parent
|
||||
* @param title title to use
|
||||
* @param project the project to browse
|
||||
* @throws IllegalArgumentException if invalid type is specified
|
||||
*/
|
||||
public ProgramFileChooser(Component parent, String title, Project project) {
|
||||
super(parent, title, DataTreeDialogType.OPEN, PROGRAM_FILE_FILTER, project);
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,6 @@ import java.awt.Component;
|
|||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.services.FileImporterService;
|
||||
import ghidra.app.util.FileOpenDataFlavorHandler;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
|
@ -61,15 +60,4 @@ abstract class AbstractFileListFlavorHandler
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected DomainFolder getDomainFolder(GTreeNode destinationNode) {
|
||||
if (destinationNode instanceof DomainFolderNode) {
|
||||
return ((DomainFolderNode) destinationNode).getDomainFolder();
|
||||
}
|
||||
else if (destinationNode instanceof DomainFileNode) {
|
||||
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
|
||||
return parent.getDomainFolder();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ public final class JavaFileListHandler extends AbstractFileListFlavorHandler {
|
|||
if (fileList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
|
||||
doImport(DataTree.getRealInternalFolderForNode(destinationNode), fileList, tool, dataTree);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,9 +38,8 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
|||
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
|
||||
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
|
||||
*/
|
||||
public static final DataFlavor linuxFileUrlFlavor =
|
||||
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
|
||||
"String file URL");
|
||||
public static final DataFlavor linuxFileUrlFlavor = new DataFlavor(
|
||||
"application/x-java-serialized-object;class=java.lang.String", "String file URL");
|
||||
|
||||
@Override
|
||||
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
|
||||
|
@ -57,7 +56,7 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
|||
if (files.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
doImport(getDomainFolder(destinationNode), files, tool, dataTree);
|
||||
doImport(DataTree.getRealInternalFolderForNode(destinationNode), files, tool, dataTree);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ public class ImporterDialog extends DialogComponentProvider {
|
|||
*/
|
||||
public void setDestinationFolder(DomainFolder folder) {
|
||||
destinationFolder = folder;
|
||||
folderNameTextField.setText(destinationFolder.toString());
|
||||
folderNameTextField.setText(destinationFolder.getPathname());
|
||||
validateFormInput();
|
||||
}
|
||||
|
||||
|
@ -521,7 +521,7 @@ public class ImporterDialog extends DialogComponentProvider {
|
|||
String parentPath = FilenameUtils.getFullPathNoEndSeparator(pathName);
|
||||
String fileOrFolderName = FilenameUtils.getName(pathName);
|
||||
DomainFolder localDestFolder =
|
||||
(parentPath != null) ? ProjectDataUtils.lookupDomainPath(destinationFolder, parentPath)
|
||||
(parentPath != null) ? ProjectDataUtils.getDomainFolder(destinationFolder, parentPath)
|
||||
: destinationFolder;
|
||||
if (localDestFolder != null) {
|
||||
if (isFolder && localDestFolder.getFolder(fileOrFolderName) != null ||
|
||||
|
|
|
@ -28,6 +28,7 @@ import docking.action.*;
|
|||
import docking.tool.ToolConstants;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.events.ProgramActivatedPluginEvent;
|
||||
|
@ -441,17 +442,14 @@ public class ImporterPlugin extends Plugin
|
|||
}
|
||||
|
||||
private static DomainFolder getFolderFromContext(ActionContext context) {
|
||||
DomainFolder folder = null;
|
||||
Object contextObj = context.getContextObject();
|
||||
if (contextObj instanceof DomainFolderNode) {
|
||||
DomainFolderNode node = (DomainFolderNode) contextObj;
|
||||
return node.getDomainFolder();
|
||||
if (contextObj instanceof GTreeNode dataTreeNode) {
|
||||
folder = DataTree.getRealInternalFolderForNode(dataTreeNode);
|
||||
}
|
||||
if (contextObj instanceof DomainFileNode) {
|
||||
DomainFileNode node = (DomainFileNode) contextObj;
|
||||
DomainFile domainFile = node.getDomainFile();
|
||||
return domainFile != null ? domainFile.getParent() : null;
|
||||
if (folder != null && folder.isInWritableProject()) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
return AppInfo.getActiveProject().getProjectData().getRootFolder();
|
||||
}
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.model.address.*;
|
||||
|
@ -628,4 +627,21 @@ public class ToyProgramBuilder extends ProgramBuilder {
|
|||
disassemble(address, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create simple Toy program with a single initialized memory block at 0x1001000-0x1002fff
|
||||
* @param programName program name
|
||||
* @param consumer object consumer responsible for releasing the returned program
|
||||
* @return new in-memory program instance
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
public static Program buildSimpleProgram(String programName, Object consumer) throws Exception {
|
||||
Objects.requireNonNull(consumer);
|
||||
ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY);
|
||||
builder.createMemory("test1", Long.toHexString(0x1001000), 0x2000);
|
||||
Program p = builder.getProgram();
|
||||
p.addConsumer(consumer);
|
||||
p.release(builder);
|
||||
return p;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ import java.awt.*;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -52,7 +52,6 @@ import ghidra.program.util.ProgramLocation;
|
|||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import util.CollectionUtils;
|
||||
|
||||
public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
|
@ -365,7 +364,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||
|
||||
assertErrorDialog("No Symbol");
|
||||
assertErrorDialog("Symbol Not Found");
|
||||
|
||||
assertTrue(spyServiceProvider.programOpened(programName));
|
||||
assertTrue(spyServiceProvider.programClosed(programName));
|
||||
|
@ -422,7 +421,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||
|
||||
assertErrorDialog("No Symbol");
|
||||
assertErrorDialog("Symbol Not Found");
|
||||
|
||||
assertTrue(spyServiceProvider.programOpened(programName));
|
||||
assertTrue(spyServiceProvider.programClosed(programName));
|
||||
|
@ -478,7 +477,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||
|
||||
assertErrorDialog("No Program");
|
||||
assertErrorDialog("Program Not Found");
|
||||
|
||||
assertFalse(spyServiceProvider.programOpened(programName));
|
||||
}
|
||||
|
@ -495,8 +494,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
||||
|
||||
// real path
|
||||
String realPath = "folder1/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, realPath);
|
||||
String realPath = "/folder1/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, realPath, program);
|
||||
|
||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -513,7 +512,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
|
||||
click(spyNavigatable, spyServiceProvider, annotatedElement);
|
||||
|
||||
assertErrorDialog("No Folder");
|
||||
assertErrorDialog("Folder Not Found");
|
||||
|
||||
assertFalse(spyServiceProvider.programOpened(otherProgramPath));
|
||||
}
|
||||
|
@ -525,7 +524,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
|
||||
|
||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||
|
||||
String annotationText = "{@program " + otherProgramPath + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -557,7 +556,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||
|
||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||
|
||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -591,7 +590,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||
String annotationPath = "\\folder1\\folder2\\program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||
|
||||
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -622,9 +621,9 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
String addresstring = "1001000";
|
||||
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||
|
||||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||
String annotationPath = "folder1\\folder2\\program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||
|
||||
String annotationText = "{@program " + annotationPath + "@" + addresstring + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -656,8 +655,8 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
String addresstring = "1001000";
|
||||
Address address = program.getAddressFactory().getAddress(addresstring);
|
||||
|
||||
String otherProgramPath = "folder1/folder2/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath);
|
||||
String otherProgramPath = "/folder1/folder2/program_f1_f2.exe";
|
||||
addFakeProgramByPath(spyServiceProvider, otherProgramPath, program);
|
||||
|
||||
String annotationText = "{@program " + otherProgramPath + "@" + addresstring + "}";
|
||||
String rawComment = "My comment - " + annotationText;
|
||||
|
@ -883,30 +882,29 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
return new FieldElement[] { fieldElement };
|
||||
}
|
||||
|
||||
private void addFakeProgramByPath(SpyServiceProvider provider, String path) {
|
||||
private void addFakeProgramByPath(SpyServiceProvider provider, String path, Program p) {
|
||||
|
||||
SpyProjectDataService spyProjectData =
|
||||
(SpyProjectDataService) provider.getService(ProjectDataService.class);
|
||||
FakeRootFolder root = spyProjectData.fakeProjectData.fakeRootFolder;
|
||||
|
||||
String parentPath = FilenameUtils.getFullPath(path);
|
||||
String programName = FilenameUtils.getName(path);
|
||||
|
||||
String[] paths = parentPath.split("/");
|
||||
TestDummyDomainFolder parent = root;
|
||||
String pathSoFar = root.getPathname();
|
||||
for (String folderName : paths) {
|
||||
pathSoFar += folderName;
|
||||
TestDummyDomainFolder folder = (TestDummyDomainFolder) root.getFolder(pathSoFar);
|
||||
if (folder == null) {
|
||||
folder = new TestDummyDomainFolder(parent, folderName);
|
||||
root.addFolder(folder);
|
||||
if (StringUtils.isBlank(path) || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
|
||||
throw new IllegalArgumentException(
|
||||
"Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
|
||||
}
|
||||
parent = folder;
|
||||
else if (path.charAt(path.length() - 1) == FileSystem.SEPARATOR_CHAR) {
|
||||
throw new IllegalArgumentException("Missing file name in path");
|
||||
}
|
||||
int ix = path.lastIndexOf(FileSystem.SEPARATOR);
|
||||
String folderPath = "/";
|
||||
if (ix > 0) {
|
||||
folderPath = path.substring(0, ix);
|
||||
}
|
||||
String programName = path.substring(ix + 1);
|
||||
|
||||
try {
|
||||
parent.createFile(programName, (DomainObject) null, TaskMonitor.DUMMY);
|
||||
DomainFolder parent = ProjectDataUtils.createDomainFolderPath(root, folderPath);
|
||||
parent.createFile(programName, p, TaskMonitor.DUMMY);
|
||||
}
|
||||
catch (Exception e) {
|
||||
failWithException("Unable to create a dummy domain file", e);
|
||||
|
@ -973,41 +971,50 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public DomainFolder getFolder(String path) {
|
||||
return fakeRootFolder.getFolder(path);
|
||||
public DomainFolder getFolder(String path, DomainFolderFilter filter) {
|
||||
return ProjectDataUtils.getDomainFolder(fakeRootFolder, path, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainFile getFile(String path, DomainFileFilter filter) {
|
||||
if (StringUtils.isBlank(path) || path.charAt(0) != FileSystem.SEPARATOR_CHAR) {
|
||||
throw new IllegalArgumentException(
|
||||
"Absolute path must begin with '" + FileSystem.SEPARATOR_CHAR + "'");
|
||||
}
|
||||
else if (path.charAt(path.length() - 1) == FileSystem.SEPARATOR_CHAR) {
|
||||
throw new IllegalArgumentException("Missing file name in path");
|
||||
}
|
||||
int ix = path.lastIndexOf(FileSystem.SEPARATOR);
|
||||
|
||||
DomainFolder folder;
|
||||
String fileName = path;
|
||||
if (ix > 0) {
|
||||
folder = getFolder(path.substring(0, ix), filter);
|
||||
fileName = path.substring(ix + 1);
|
||||
}
|
||||
else {
|
||||
folder = getRootFolder();
|
||||
}
|
||||
if (folder != null) {
|
||||
DomainFile file = folder.getFile(fileName);
|
||||
if (file != null && filter.accept(file)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeRootFolder extends TestDummyDomainFolder {
|
||||
|
||||
private List<TestDummyDomainFolder> folders = CollectionUtils.asList(this);
|
||||
|
||||
private List<TestDummyDomainFile> folderFiles =
|
||||
CollectionUtils.asList(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME));
|
||||
|
||||
public FakeRootFolder() {
|
||||
super(null, "Fake Root Folder");
|
||||
}
|
||||
|
||||
void addFolder(TestDummyDomainFolder f) {
|
||||
folders.add(f);
|
||||
files.add(new TestDummyDomainFile(this, OTHER_PROGRAM_NAME, "Program"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DomainFile[] getFiles() {
|
||||
return folderFiles.toArray(new TestDummyDomainFile[folderFiles.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DomainFolder getFolder(String path) {
|
||||
for (TestDummyDomainFolder folder : folders) {
|
||||
String folderPath = folder.getPathname();
|
||||
if (folderPath.equals(path)) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
public boolean isInWritableProject() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -87,10 +87,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
showFiltered("tN");
|
||||
|
||||
JTree tree = getJTree();
|
||||
List<String> expectedFilteredNames = names.stream()
|
||||
.filter(s -> s.startsWith("tN"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
List<String> expectedFilteredNames =
|
||||
names.stream().filter(s -> s.startsWith("tN")).sorted().collect(Collectors.toList());
|
||||
|
||||
TreeModel model = tree.getModel();
|
||||
GTreeNode root = (GTreeNode) model.getRoot();
|
||||
|
@ -424,8 +422,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
private void showFiltered(final String startsWith) {
|
||||
Swing.runLater(() -> {
|
||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog",
|
||||
OPEN, f -> f.getName().startsWith(startsWith));
|
||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", OPEN,
|
||||
f -> f.getName().startsWith(startsWith));
|
||||
dialog.showComponent();
|
||||
});
|
||||
waitForSwing();
|
||||
|
|
|
@ -732,11 +732,22 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
|||
performAction(selectAction, getTreeActionContext(), true);
|
||||
waitForTree();
|
||||
|
||||
// NOTE: All nodes except the root node should be selected.
|
||||
// Root is not selected to allow for most popup actions to
|
||||
// be enabled and work as expected
|
||||
|
||||
BreadthFirstIterator it = new BreadthFirstIterator(rootNode);
|
||||
int count = 0;
|
||||
while (it.hasNext()) {
|
||||
GTreeNode node = it.next();
|
||||
assertTrue(tree.isPathSelected(node.getTreePath()));
|
||||
if (tree.isPathSelected(node.getTreePath())) {
|
||||
++count;
|
||||
}
|
||||
else {
|
||||
assertTrue(node.isRoot());
|
||||
}
|
||||
}
|
||||
assertEquals(7, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -954,11 +965,12 @@ public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTe
|
|||
for (TreePath path : paths) {
|
||||
|
||||
GTreeNode node = (GTreeNode) path.getLastPathComponent();
|
||||
if (node instanceof DomainFileNode) {
|
||||
fileList.add(((DomainFileNode) node).getDomainFile());
|
||||
if (node instanceof DomainFileNode fileNode) {
|
||||
// NOTE: File may be a linked-folder. Treatment as folder or file depends on action
|
||||
fileList.add(fileNode.getDomainFile());
|
||||
}
|
||||
else if (node instanceof DomainFolderNode) {
|
||||
folderList.add(((DomainFolderNode) node).getDomainFolder());
|
||||
else if (node instanceof DomainFolderNode folderNode) {
|
||||
folderList.add(folderNode.getDomainFolder());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,9 +15,8 @@
|
|||
*/
|
||||
package ghidra.file.formats.android.oat.bundle;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
@ -30,6 +29,7 @@ import ghidra.file.formats.android.oat.OatHeader;
|
|||
import ghidra.file.formats.android.vdex.*;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -45,8 +45,7 @@ public class FullOatBundle implements OatBundle {
|
|||
|
||||
private boolean isLittleEndian;
|
||||
|
||||
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor,
|
||||
MessageLog log) {
|
||||
FullOatBundle(Program oatProgram, OatHeader oatHeader, TaskMonitor monitor, MessageLog log) {
|
||||
|
||||
this.oatProgram = oatProgram;
|
||||
this.oatHeader = oatHeader;
|
||||
|
@ -120,12 +119,11 @@ public class FullOatBundle implements OatBundle {
|
|||
DomainFolder parentFolder = domainFile.getParent();
|
||||
|
||||
//first, look in current project for VDEX file....
|
||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder,
|
||||
vdexProgramName, monitor, log)) {
|
||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder, vdexProgramName, monitor, log)) {
|
||||
return;
|
||||
}
|
||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(),
|
||||
vdexProgramName, monitor, log)) {
|
||||
if (lookInProjectFolder(HeaderType.VDEX, parentFolder.getParent(), vdexProgramName, monitor,
|
||||
log)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -140,8 +138,8 @@ public class FullOatBundle implements OatBundle {
|
|||
break;
|
||||
}
|
||||
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
||||
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(),
|
||||
monitor, log);
|
||||
lookInProjectFolder(HeaderType.DEX, odexApkFolder, file.getName(), monitor,
|
||||
log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,8 +151,8 @@ public class FullOatBundle implements OatBundle {
|
|||
break;
|
||||
}
|
||||
if (file.getName().startsWith(CLASSES) && file.getName().endsWith(DEX)) {
|
||||
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(),
|
||||
monitor, log);
|
||||
lookInProjectFolder(HeaderType.DEX, apkOrJarFolder, file.getName(), monitor,
|
||||
log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,8 +164,8 @@ public class FullOatBundle implements OatBundle {
|
|||
break;
|
||||
}
|
||||
if (file.getName().startsWith(CDEX)) {
|
||||
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(),
|
||||
monitor, log);
|
||||
lookInProjectFolder(HeaderType.CDEX, appVdexFolder, file.getName(), monitor,
|
||||
log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,12 +181,11 @@ public class FullOatBundle implements OatBundle {
|
|||
DomainFolder parentFolder = domainFile.getParent();
|
||||
|
||||
//first, look in current project for ART file....
|
||||
if (lookInProjectFolder(HeaderType.ART, parentFolder,
|
||||
artProgramName, monitor, log)) {
|
||||
if (lookInProjectFolder(HeaderType.ART, parentFolder, artProgramName, monitor, log)) {
|
||||
return;
|
||||
}
|
||||
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(),
|
||||
artProgramName, monitor, log)) {
|
||||
if (lookInProjectFolder(HeaderType.ART, parentFolder.getParent(), artProgramName, monitor,
|
||||
log)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -203,14 +200,15 @@ public class FullOatBundle implements OatBundle {
|
|||
* @param log the message log
|
||||
*/
|
||||
private boolean lookInProjectFolder(HeaderType type, DomainFolder parentFolder,
|
||||
String programName,
|
||||
TaskMonitor monitor, MessageLog log) {
|
||||
String programName, TaskMonitor monitor, MessageLog log) {
|
||||
|
||||
DomainFile child = parentFolder.getFile(programName);
|
||||
if (child != null) {
|
||||
DomainFile file = parentFolder.getFile(programName);
|
||||
// Constrain to Program files only and not program link-files
|
||||
if (file != null &&
|
||||
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType())) {
|
||||
Program program = null;
|
||||
try {
|
||||
program = (Program) child.getDomainObject(this, true, true, monitor);
|
||||
program = (Program) file.getDomainObject(this, true, true, monitor);
|
||||
ByteProvider provider =
|
||||
MemoryByteProvider.createProgramHeaderByteProvider(program, false);
|
||||
return makeHeader(type, programName, provider, monitor);
|
||||
|
|
|
@ -22,6 +22,7 @@ import ghidra.file.analyzers.FileFormatAnalyzer;
|
|||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.database.ProgramContentHandler;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.PointerDataType;
|
||||
import ghidra.program.model.lang.Processor;
|
||||
|
@ -295,6 +296,7 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
|||
|
||||
private DestinationProgramInfo recurseFolder(DomainFolder folder, Address destinationAddress,
|
||||
ProgramManager programManager, TaskMonitor monitor) {
|
||||
// NOTE: All folder-links and file-links are ignored
|
||||
DomainFolder[] folders = folder.getFolders();
|
||||
for (DomainFolder child : folders) {
|
||||
if (monitor.isCancelled()) {
|
||||
|
@ -311,15 +313,17 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
|||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
DomainObject domainObject = null;
|
||||
if (!file.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
|
||||
continue;
|
||||
}
|
||||
Program program = null;
|
||||
try {
|
||||
domainObject = file.getDomainObject(this, true /* upgrade */,
|
||||
program = (Program) file.getDomainObject(this, true /* upgrade */,
|
||||
false /* do not recover */, monitor);
|
||||
if (domainObject instanceof Program) {
|
||||
Program program = (Program) domainObject;
|
||||
if (program.getMemory().contains(destinationAddress)) {
|
||||
if (programManager != null) {
|
||||
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);//once program is located, open it, so lookup is faster next time!
|
||||
//once program is located, open it, so lookup is faster next time!
|
||||
programManager.openProgram(program, ProgramManager.OPEN_VISIBLE);
|
||||
}
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
Symbol symbol = symbolTable.getPrimarySymbol(destinationAddress);
|
||||
|
@ -328,13 +332,12 @@ public class iOS_KextStubFixupAnalyzer extends FileFormatAnalyzer {
|
|||
symbolName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.warn(this, e);
|
||||
}
|
||||
finally {
|
||||
if (domainObject != null) {
|
||||
domainObject.release(this);
|
||||
if (program != null) {
|
||||
program.release(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ public class GFileSystemLoadKernelTask extends Task {
|
|||
|
||||
ProjectIndexService projectIndex = ProjectIndexService.getIndexFor(project);
|
||||
DomainFile existingDF = projectIndex.findFirstByFSRL(file.getFSRL());
|
||||
if ( existingDF != null && programManager != null ) {
|
||||
if (existingDF != null && programManager != null) {
|
||||
programManager.openProgram(existingDF);
|
||||
return;
|
||||
}
|
||||
|
@ -138,6 +138,9 @@ public class GFileSystemLoadKernelTask extends Task {
|
|||
AppInfo.getActiveProject().getProjectData().getRootFolder(),
|
||||
file.getParentFile().getPath());
|
||||
String fileName = ProjectDataUtils.getUniqueName(folder, program.getName());
|
||||
if (fileName == null) {
|
||||
throw new IOException("Unable to find unique name for " + program.getName());
|
||||
}
|
||||
|
||||
GhidraProgramUtilities.markProgramAnalyzed(program);
|
||||
|
||||
|
|
|
@ -250,12 +250,12 @@ public class GhidraServer extends UnicastRemoteObject implements GhidraServerHan
|
|||
}
|
||||
|
||||
@Override
|
||||
public void checkCompatibility(int serverInterfaceVersion) throws RemoteException {
|
||||
if (serverInterfaceVersion > INTERFACE_VERSION) {
|
||||
public void checkCompatibility(int minServerInterfaceVersion) throws RemoteException {
|
||||
if (minServerInterfaceVersion > INTERFACE_VERSION) {
|
||||
throw new RemoteException(
|
||||
"Incompatible server interface, a newer Ghidra Server version is required.");
|
||||
}
|
||||
else if (serverInterfaceVersion < INTERFACE_VERSION) {
|
||||
else if (minServerInterfaceVersion < MINIMUM_INTERFACE_VERSION) {
|
||||
throw new RemoteException(
|
||||
"Incompatible server interface, the minimum supported Ghidra version is " +
|
||||
MIN_GHIDRA_VERSION);
|
||||
|
|
|
@ -381,6 +381,22 @@ public class RepositoryHandleImpl extends UnicastRemoteObject
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createTextDataFile(String parentPath, String itemName, String fileID,
|
||||
String contentType, String textData, String comment)
|
||||
throws InvalidNameException, IOException {
|
||||
synchronized (syncObject) {
|
||||
validate();
|
||||
repository.validateWritePrivilege(currentUser);
|
||||
RepositoryFolder folder = repository.getFolder(currentUser, parentPath, true);
|
||||
if (folder == null) {
|
||||
throw new IOException("Failed to create repository Folder " + parentPath);
|
||||
}
|
||||
folder.createTextDataFile(itemName, fileID, contentType, textData, comment,
|
||||
currentUser);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteManagedBufferFileHandle createDatabase(String parentPath, String itemName,
|
||||
String fileID, int bufferSize, String contentType, String projectPath)
|
||||
|
|
|
@ -39,7 +39,7 @@ public class RepositoryFile {
|
|||
private LocalFileSystem fileSystem;
|
||||
private RepositoryFolder parent;
|
||||
private String name;
|
||||
private LocalDatabaseItem databaseItem;
|
||||
private LocalFolderItem folderItem;
|
||||
private RepositoryItem repositoryItem;
|
||||
private boolean deleted = false;
|
||||
|
||||
|
@ -69,11 +69,10 @@ public class RepositoryFile {
|
|||
if (deleted) {
|
||||
throw new FileNotFoundException(getPathname() + " not found");
|
||||
}
|
||||
if (databaseItem == null) {
|
||||
if (folderItem == null) {
|
||||
repositoryItem = null;
|
||||
LocalFolderItem folderItem = fileSystem.getItem(parent.getPathname(), name);
|
||||
if (folderItem == null || !folderItem.isVersioned() ||
|
||||
!(folderItem instanceof LocalDatabaseItem)) {
|
||||
folderItem = fileSystem.getItem(parent.getPathname(), name);
|
||||
if (folderItem == null) {
|
||||
// must build pathname just in case folderItem does not exist
|
||||
String pathname = parent.getPathname();
|
||||
if (pathname.length() != 1) {
|
||||
|
@ -84,7 +83,6 @@ public class RepositoryFile {
|
|||
"file is corrupt or unsupported", null);
|
||||
throw new FileNotFoundException(pathname + " is corrupt or unsupported");
|
||||
}
|
||||
this.databaseItem = (LocalDatabaseItem) folderItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,16 +125,33 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
try {
|
||||
validate();
|
||||
if (repositoryItem == null) {
|
||||
repositoryItem =
|
||||
new RepositoryItem(parent.getPathname(), name, databaseItem.getFileID(),
|
||||
RepositoryItem.DATABASE, databaseItem.getContentType(),
|
||||
databaseItem.getCurrentVersion(), databaseItem.lastModified());
|
||||
if (repositoryItem == null && folderItem != null) {
|
||||
String textData = null;
|
||||
int itemType = -1;
|
||||
if (folderItem instanceof DatabaseItem) {
|
||||
itemType = RepositoryItem.DATABASE;
|
||||
}
|
||||
else if (folderItem instanceof TextDataItem textItem) {
|
||||
itemType = RepositoryItem.TEXT_DATA_FILE;
|
||||
textData = textItem.getTextData();
|
||||
}
|
||||
else {
|
||||
repository.log(getPathname(),
|
||||
"Unsupported item type: " + folderItem.getClass().getSimpleName(),
|
||||
null);
|
||||
}
|
||||
|
||||
repositoryItem = new RepositoryItem(parent.getPathname(), name,
|
||||
folderItem.getFileID(), itemType, folderItem.getContentType(),
|
||||
folderItem.getCurrentVersion(), folderItem.lastModified(), textData);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
repository.log(getPathname(), "Item failure: " + e.getMessage(), null);
|
||||
}
|
||||
if (repository == null) {
|
||||
repositoryItem = new RepositoryItem(parent.getPathname(), name, null,
|
||||
RepositoryItem.DATABASE, "INVALID", 0, 0);
|
||||
RepositoryItem.FILE, "INVALID", 0, 0, null);
|
||||
}
|
||||
return repositoryItem;
|
||||
}
|
||||
|
@ -157,9 +172,14 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateReadPrivilege(user);
|
||||
if (!(folderItem instanceof LocalDatabaseItem databaseItem)) {
|
||||
throw new IOException(
|
||||
"Unsupported operation for " + folderItem.getClass().getSimpleName());
|
||||
}
|
||||
LocalManagedBufferFile bf = databaseItem.open(version, minChangeDataVer);
|
||||
repository.log(getPathname(), "version " +
|
||||
(version < 0 ? databaseItem.getCurrentVersion() : version) + " opened read-only",
|
||||
repository.log(
|
||||
getPathname(), "version " +
|
||||
(version < 0 ? folderItem.getCurrentVersion() : version) + " opened read-only",
|
||||
user);
|
||||
return bf;
|
||||
}
|
||||
|
@ -177,7 +197,11 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateWritePrivilege(user);
|
||||
ItemCheckoutStatus coStatus = databaseItem.getCheckout(checkoutId);
|
||||
if (!(folderItem instanceof LocalDatabaseItem databaseItem)) {
|
||||
throw new IOException(
|
||||
"Unsupported operation for " + folderItem.getClass().getSimpleName());
|
||||
}
|
||||
ItemCheckoutStatus coStatus = folderItem.getCheckout(checkoutId);
|
||||
if (coStatus == null) {
|
||||
throw new IOException("Illegal checkin");
|
||||
}
|
||||
|
@ -202,7 +226,7 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateReadPrivilege(user);
|
||||
return databaseItem.getVersions();
|
||||
return folderItem.getVersions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +240,7 @@ public class RepositoryFile {
|
|||
public long length() throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
validate();
|
||||
return databaseItem.length();
|
||||
return folderItem.length();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,7 +258,7 @@ public class RepositoryFile {
|
|||
User userObj = repository.validateWritePrivilege(user);
|
||||
|
||||
if (!userObj.isAdmin()) {
|
||||
Version[] versions = databaseItem.getVersions();
|
||||
Version[] versions = folderItem.getVersions();
|
||||
if (deleteVersion == -1) {
|
||||
for (Version version : versions) {
|
||||
if (!user.equals(version.getUser())) {
|
||||
|
@ -259,21 +283,13 @@ public class RepositoryFile {
|
|||
throw new IOException("Only the oldest or latest version may be deleted");
|
||||
}
|
||||
}
|
||||
String oldPath = getPathname();
|
||||
if (databaseItem == null) {
|
||||
// forced removal by repo Admin
|
||||
|
||||
}
|
||||
else {
|
||||
databaseItem.delete(deleteVersion, user);
|
||||
if (folderItem != null) {
|
||||
folderItem.delete(deleteVersion, user);
|
||||
}
|
||||
deleted = true;
|
||||
repositoryItem = null;
|
||||
parent.fileDeleted(this);
|
||||
RepositoryFile newRf = parent.getFile(name);
|
||||
if (newRf == null) {
|
||||
RepositoryManager.log(repository.getName(), oldPath, "file deleted", user);
|
||||
}
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +336,7 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateWritePrivilege(user); // don't allow checkout if read-only
|
||||
ItemCheckoutStatus coStatus = databaseItem.checkout(checkoutType, user, projectPath);
|
||||
ItemCheckoutStatus coStatus = folderItem.checkout(checkoutType, user, projectPath);
|
||||
if (coStatus != null && checkoutType != CheckoutType.NORMAL && repositoryItem != null &&
|
||||
repositoryItem.getFileID() == null) {
|
||||
repositoryItem = null; // force refresh since fileID should get reset
|
||||
|
@ -340,7 +356,7 @@ public class RepositoryFile {
|
|||
throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
validate();
|
||||
databaseItem.updateCheckoutVersion(checkoutId, checkoutVersion, user);
|
||||
folderItem.updateCheckoutVersion(checkoutId, checkoutVersion, user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,14 +370,14 @@ public class RepositoryFile {
|
|||
public void terminateCheckout(long checkoutId, String user, boolean notify) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
validate();
|
||||
ItemCheckoutStatus coStatus = databaseItem.getCheckout(checkoutId);
|
||||
ItemCheckoutStatus coStatus = folderItem.getCheckout(checkoutId);
|
||||
if (coStatus != null) {
|
||||
User userObj = repository.getUser(user);
|
||||
if (!userObj.isAdmin() && !coStatus.getUser().equals(user)) {
|
||||
throw new IOException(
|
||||
"Undo-checkout not permitted, checkout was made by " + coStatus.getUser());
|
||||
}
|
||||
databaseItem.terminateCheckout(checkoutId, notify);
|
||||
folderItem.terminateCheckout(checkoutId, notify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,7 +394,7 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateReadPrivilege(user);
|
||||
return databaseItem.getCheckout(checkoutId);
|
||||
return folderItem.getCheckout(checkoutId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,7 +409,7 @@ public class RepositoryFile {
|
|||
synchronized (fileSystem) {
|
||||
validate();
|
||||
repository.validateReadPrivilege(user);
|
||||
return databaseItem.getCheckouts();
|
||||
return folderItem.getCheckouts();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,7 +421,7 @@ public class RepositoryFile {
|
|||
public boolean hasCheckouts() throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
validate();
|
||||
return databaseItem.hasCheckouts();
|
||||
return folderItem.hasCheckouts();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +433,7 @@ public class RepositoryFile {
|
|||
public boolean isCheckinActive() throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
validate();
|
||||
return databaseItem.isCheckinActive();
|
||||
return folderItem.isCheckinActive();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,7 +452,7 @@ public class RepositoryFile {
|
|||
void pathChanged() {
|
||||
synchronized (fileSystem) {
|
||||
repositoryItem = null;
|
||||
databaseItem = null;
|
||||
folderItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,7 @@ import org.apache.logging.log4j.Logger;
|
|||
|
||||
import db.buffers.LocalManagedBufferFile;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.local.LocalFolderItem;
|
||||
import ghidra.framework.store.local.*;
|
||||
import ghidra.server.Repository;
|
||||
import ghidra.server.RepositoryManager;
|
||||
import ghidra.util.InvalidNameException;
|
||||
|
@ -94,20 +93,20 @@ public class RepositoryFolder {
|
|||
private void init() throws IOException {
|
||||
String path = getPathname();
|
||||
String[] names = fileSystem.getFolderNames(path);
|
||||
for (String name2 : names) {
|
||||
RepositoryFolder subfolder = new RepositoryFolder(repository, fileSystem, this, name2);
|
||||
folderMap.put(name2, subfolder);
|
||||
for (String folderName : names) {
|
||||
RepositoryFolder subfolder =
|
||||
new RepositoryFolder(repository, fileSystem, this, folderName);
|
||||
folderMap.put(folderName, subfolder);
|
||||
}
|
||||
names = fileSystem.getItemNames(path);
|
||||
int badItemCount = 0;
|
||||
for (String name2 : names) {
|
||||
LocalFolderItem item = fileSystem.getItem(path, name2);
|
||||
if (item == null || !(item instanceof DatabaseItem)) {
|
||||
for (String itemName : names) {
|
||||
LocalFolderItem item = fileSystem.getItem(path, itemName);
|
||||
if (item == null || (item instanceof UnknownFolderItem)) {
|
||||
++badItemCount;
|
||||
continue;
|
||||
}
|
||||
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, name2);
|
||||
fileMap.put(name2, rf);
|
||||
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
|
||||
fileMap.put(itemName, rf);
|
||||
}
|
||||
if (badItemCount != 0) {
|
||||
log.error("Repository '" + repository.getName() + "' contains " + badItemCount +
|
||||
|
@ -217,7 +216,7 @@ public class RepositoryFolder {
|
|||
if (fileSystem.fileExists(getPathname(), fileName)) {
|
||||
try {
|
||||
LocalFolderItem item = fileSystem.getItem(getPathname(), fileName);
|
||||
if (item == null || !(item instanceof DatabaseItem)) {
|
||||
if (item == null) {
|
||||
log.error("Repository '" + repository.getName() + "' contains bad item: " +
|
||||
makePathname(getPathname(), fileName));
|
||||
return null;
|
||||
|
@ -262,6 +261,41 @@ public class RepositoryFolder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new text data file within the specified parent folder.
|
||||
* @param itemName new data file name
|
||||
* @param fileID file ID to be associated with new file or null
|
||||
* @param contentType application defined content type
|
||||
* @param textData text data (required)
|
||||
* @param comment file comment (may be null)
|
||||
* @param user user who is initiating request
|
||||
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||
* @throws InvalidNameException if the name has illegal characters.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public void createTextDataFile(String itemName, String fileID, String contentType,
|
||||
String textData, String comment, String user) throws InvalidNameException, IOException {
|
||||
synchronized (fileSystem) {
|
||||
repository.validate();
|
||||
repository.validateWritePrivilege(user);
|
||||
if (getFile(itemName) != null) {
|
||||
throw new DuplicateFileException(itemName + " already exists");
|
||||
}
|
||||
|
||||
LocalTextDataItem textDataItem = fileSystem.createTextDataItem(getPathname(), itemName,
|
||||
fileID, contentType, textData, null); // comment conveyed with Version info below
|
||||
|
||||
Version singleVersion = new Version(1, System.currentTimeMillis(), user, comment);
|
||||
textDataItem.setVersionInfo(singleVersion);
|
||||
|
||||
RepositoryFile rf = new RepositoryFile(repository, fileSystem, this, itemName);
|
||||
fileMap.put(itemName, rf);
|
||||
|
||||
RepositoryManager.log(repository.getName(), makePathname(getPathname(), itemName),
|
||||
"file created", user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new database file/item within this folder.
|
||||
* @param itemName name of new database
|
||||
|
@ -445,4 +479,5 @@ public class RepositoryFolder {
|
|||
: parentPath;
|
||||
return path + FileSystem.SEPARATOR + childName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
//
|
||||
//@category Version Tracking
|
||||
import ghidra.app.script.GhidraScript;
|
||||
import ghidra.feature.vt.api.db.VTSessionContentHandler;
|
||||
import ghidra.feature.vt.api.db.VTSessionDB;
|
||||
import ghidra.feature.vt.api.main.VTSession;
|
||||
import ghidra.feature.vt.api.util.VTOptions;
|
||||
|
@ -108,7 +109,7 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
|||
@Override
|
||||
public void run() throws Exception {
|
||||
|
||||
if(currentProgram == null) {
|
||||
if (currentProgram == null) {
|
||||
println("Please open the destination program.");
|
||||
return;
|
||||
}
|
||||
|
@ -175,8 +176,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
|||
return;
|
||||
}
|
||||
|
||||
Program sourceProgram = (Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded,
|
||||
false, monitor);
|
||||
Program sourceProgram =
|
||||
(Program) sourceProgramDF.getDomainObject(this, autoUpgradeIfNeeded, false, monitor);
|
||||
|
||||
VTSession session = null;
|
||||
try {
|
||||
|
@ -258,19 +259,8 @@ public class AutoVersionTrackingScript extends GhidraScript {
|
|||
* @throws CancelledException if cancelled
|
||||
*/
|
||||
private boolean hasExistingSession(String name, DomainFolder folder) throws CancelledException {
|
||||
|
||||
DomainFile[] files = folder.getFiles();
|
||||
|
||||
for (DomainFile file : files) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
if (file.getName().equals(name)) {
|
||||
if (file.getContentType().equals("VersionTracking")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
DomainFile file = folder.getFile(name);
|
||||
return file != null && file.getContentType().equals(VTSessionContentHandler.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -333,7 +333,7 @@ public class VTSessionDB extends DomainObjectAdapterDB implements VTSession {
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), type, "open",
|
||||
e);
|
||||
false, e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Can't open " + type + ": " + domainFile.getName(),
|
||||
|
|
|
@ -25,8 +25,8 @@ import ghidra.feature.vt.api.main.VTSession;
|
|||
import ghidra.feature.vt.gui.plugin.VTController;
|
||||
import ghidra.feature.vt.gui.plugin.VTPlugin;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.DefaultDomainFileFilter;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFileFilter;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.HelpLocation;
|
||||
|
||||
|
@ -48,7 +48,7 @@ public class OpenVersionTrackingSessionAction extends DockingAction {
|
|||
PluginTool tool = controller.getTool();
|
||||
DataTreeDialog dialog =
|
||||
new DataTreeDialog(tool.getToolFrame(), "Open Version Tracking Session", OPEN,
|
||||
new VTDomainFileFilter());
|
||||
new DefaultDomainFileFilter(VTSession.class, true));
|
||||
|
||||
tool.showDialog(dialog);
|
||||
if (!dialog.wasCancelled()) {
|
||||
|
@ -57,16 +57,4 @@ public class OpenVersionTrackingSessionAction extends DockingAction {
|
|||
}
|
||||
}
|
||||
|
||||
class VTDomainFileFilter implements DomainFileFilter {
|
||||
@Override
|
||||
public boolean accept(DomainFile f) {
|
||||
Class<?> c = f.getDomainObjectClass();
|
||||
return VTSession.class.isAssignableFrom(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean followLinkedFolders() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,7 +217,7 @@ public class VTControllerImpl
|
|||
}
|
||||
catch (VersionException e) {
|
||||
VersionExceptionHandler.showVersionError(null, domainFile.getName(), "VT Session",
|
||||
"open", e);
|
||||
"open", false, e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Can't open VT Session: " + domainFile.getName(),
|
||||
|
|
|
@ -31,8 +31,7 @@ import generic.theme.GIcon;
|
|||
import generic.theme.GThemeDefaults.Ids.Fonts;
|
||||
import generic.theme.Gui;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.StringUtilities;
|
||||
import utility.function.Callback;
|
||||
|
||||
|
@ -230,8 +229,8 @@ public class SessionConfigurationPanel extends JPanel {
|
|||
JButton button = new BrowseButton();
|
||||
button.setName("SOURCE_BUTTON");
|
||||
button.addActionListener(e -> {
|
||||
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
|
||||
"a source program", VTWizardUtils.PROGRAM_FILTER, null);
|
||||
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
|
||||
"a source program", null);
|
||||
if (programFile != null) {
|
||||
setSourceFile(programFile);
|
||||
statusChangedCallback.call();
|
||||
|
@ -244,8 +243,8 @@ public class SessionConfigurationPanel extends JPanel {
|
|||
JButton button = new BrowseButton();
|
||||
button.setName("DESTINATION_BUTTON");
|
||||
button.addActionListener(e -> {
|
||||
DomainFile programFile = VTWizardUtils.chooseDomainFile(SessionConfigurationPanel.this,
|
||||
"a destination program", VTWizardUtils.PROGRAM_FILTER, null);
|
||||
DomainFile programFile = VTWizardUtils.chooseProgramFile(SessionConfigurationPanel.this,
|
||||
"a destination program", null);
|
||||
if (programFile != null) {
|
||||
setDestinationFile(programFile);
|
||||
statusChangedCallback.call();
|
||||
|
|
|
@ -25,8 +25,7 @@ import docking.widgets.OptionDialog;
|
|||
import ghidra.feature.vt.api.main.VTSession;
|
||||
import ghidra.feature.vt.gui.task.SaveTask;
|
||||
import ghidra.framework.main.DataTreeDialog;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFileFilter;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
|
@ -37,29 +36,13 @@ public class VTWizardUtils {
|
|||
DomainFile df;
|
||||
}
|
||||
|
||||
public static final DomainFileFilter VT_SESSION_FILTER = new DomainFileFilter() {
|
||||
public static final DomainFileFilter VT_SESSION_FILTER =
|
||||
new DefaultDomainFileFilter(VTSession.class, true);
|
||||
|
||||
@Override
|
||||
public boolean accept(DomainFile df) {
|
||||
return VTSession.class.isAssignableFrom(df.getDomainObjectClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean followLinkedFolders() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
public static final DomainFileFilter PROGRAM_FILTER = f -> {
|
||||
return Program.class.isAssignableFrom(f.getDomainObjectClass());
|
||||
};
|
||||
|
||||
public static DomainFile chooseDomainFile(Component parent, String domainIdentifier,
|
||||
DomainFileFilter filter, DomainFile fileToSelect) {
|
||||
final DataTreeDialog dataTreeDialog = filter == null
|
||||
? new DataTreeDialog(parent, "Choose " + domainIdentifier, OPEN)
|
||||
: new DataTreeDialog(parent, "Choose " + domainIdentifier, OPEN,
|
||||
filter);
|
||||
public static DomainFile chooseProgramFile(Component parent, String domainIdentifier,
|
||||
DomainFile fileToSelect) {
|
||||
final DataTreeDialog dataTreeDialog = new DataTreeDialog(parent,
|
||||
"Choose " + domainIdentifier, OPEN, new DefaultDomainFileFilter(Program.class, true));
|
||||
final DomainFileBox box = new DomainFileBox();
|
||||
dataTreeDialog.addOkActionListener(new ActionListener() {
|
||||
@Override
|
||||
|
|
|
@ -34,7 +34,7 @@ import ghidra.program.model.mem.Memory;
|
|||
import ghidra.program.model.mem.StubMemory;
|
||||
import ghidra.program.model.symbol.*;
|
||||
|
||||
public class VTBaseTestCase extends AbstractGenericTest {
|
||||
public abstract class VTBaseTestCase extends AbstractGenericTest {
|
||||
|
||||
private DomainFile sourceDomainFile = new TestDummyDomainFile(null, "SourceDomainFile") {
|
||||
@Override
|
||||
|
|
|
@ -448,7 +448,14 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
|||
int count = 1;
|
||||
for (GTreeNode child : children) {
|
||||
monitor.checkCancelled();
|
||||
if (child.isAutoExpandPermitted()) {
|
||||
// count the child and its children
|
||||
count += child.loadAll(monitor);
|
||||
}
|
||||
else {
|
||||
// count just the child
|
||||
++count;
|
||||
}
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
return count;
|
||||
|
|
|
@ -22,6 +22,9 @@ import docking.widgets.tree.GTreeNode;
|
|||
/**
|
||||
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
||||
* return in breadth first order.
|
||||
* <br>
|
||||
* NOTE: Iterator will not include children of a node where {@link GTreeNode#isAutoExpandPermitted()}
|
||||
* returns false.
|
||||
*/
|
||||
public class BreadthFirstIterator implements Iterator<GTreeNode> {
|
||||
private Queue<GTreeNode> nodeQueue = new LinkedList<GTreeNode>();
|
||||
|
@ -39,7 +42,7 @@ public class BreadthFirstIterator implements Iterator<GTreeNode> {
|
|||
@Override
|
||||
public GTreeNode next() {
|
||||
lastNode = nodeQueue.poll();
|
||||
if (lastNode != null) {
|
||||
if (lastNode != null && lastNode.isAutoExpandPermitted()) {
|
||||
List<GTreeNode> children = lastNode.getChildren();
|
||||
nodeQueue.addAll(children);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ import docking.widgets.tree.GTreeNode;
|
|||
/**
|
||||
* Implements an iterator over all GTreeNodes in some gTree (or subtree). The nodes are
|
||||
* return in depth first order.
|
||||
* <br>
|
||||
* NOTE: Iterator will not include children of a node where {@link GTreeNode#isAutoExpandPermitted()}
|
||||
* returns false.
|
||||
*/
|
||||
public class DepthFirstIterator implements Iterator<GTreeNode> {
|
||||
private Stack<Iterator<GTreeNode>> stack = new Stack<>();
|
||||
|
@ -49,7 +52,7 @@ public class DepthFirstIterator implements Iterator<GTreeNode> {
|
|||
it = stack.pop();
|
||||
}
|
||||
lastNode = it.next();
|
||||
if (lastNode.getChildCount() > 0) {
|
||||
if (lastNode.isAutoExpandPermitted() && lastNode.getChildCount() > 0) {
|
||||
if (it.hasNext()) {
|
||||
stack.push(it);
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
|
||||
/**
|
||||
* Returns true if connection recently was lost unexpectedly
|
||||
* @return true if connection recently was lost unexpectedly
|
||||
*/
|
||||
public boolean hadUnexpectedDisconnect() {
|
||||
return unexpectedDisconnect;
|
||||
|
@ -151,6 +152,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
|
||||
/**
|
||||
* Returns true if connected.
|
||||
* @return true if connected.
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return repository != null;
|
||||
|
@ -177,7 +179,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
if (repository == null) {
|
||||
serverAdapter.connect(); // may cause auto-reconnect of repository
|
||||
}
|
||||
if (repository == null) {
|
||||
if (repository == null && serverAdapter.isConnected()) {
|
||||
repository = serverAdapter.getRepositoryHandle(name);
|
||||
unexpectedDisconnect = false;
|
||||
if (repository == null) {
|
||||
|
@ -193,8 +195,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
|
||||
/**
|
||||
* Event reader for change dispatcher.
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @return events
|
||||
* @throws InterruptedIOException if repository handle is closed
|
||||
*/
|
||||
RepositoryChangeEvent[] getEvents() throws InterruptedIOException {
|
||||
|
@ -227,21 +228,24 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns repository name
|
||||
* Get the associated repository name
|
||||
* @return repository name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server adapter
|
||||
* Get the associated server adapter
|
||||
* @return server adapter
|
||||
*/
|
||||
public RepositoryServerAdapter getServer() {
|
||||
return serverAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server information
|
||||
* Returns associated server information
|
||||
* @return server information
|
||||
*/
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverAdapter.getServerInfo();
|
||||
|
@ -280,9 +284,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns repository user object.
|
||||
* Returns repository connected user object.
|
||||
* @return connected user object
|
||||
* @throws UserAccessException user no longer has any permission to use repository.
|
||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||
* @throws IOException if an IO error occurs
|
||||
* @see ghidra.framework.remote.RemoteRepositoryHandle#getUser()
|
||||
*/
|
||||
public User getUser() throws IOException {
|
||||
|
@ -305,7 +311,7 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
|
||||
/**
|
||||
* @return true if anonymous access allowed by this repository
|
||||
* @throws IOException
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public boolean anonymousAccessAllowed() throws IOException {
|
||||
synchronized (serverAdapter) {
|
||||
|
@ -323,10 +329,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns list of repository users.
|
||||
* @throws IOException
|
||||
* Returns list of repository users with repository access permission
|
||||
* @return return users with repository access permission
|
||||
* @throws UserAccessException user no longer has any permission to use repository.
|
||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||
* @throws IOException if an IO error occurs
|
||||
* @see RemoteRepositoryHandle#getUserList()
|
||||
*/
|
||||
public User[] getUserList() throws IOException {
|
||||
|
@ -345,10 +352,11 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns list of all users known to server.
|
||||
* @throws IOException
|
||||
* Returns list of all user names known to server.
|
||||
* @return list of all user names known to server.
|
||||
* @throws UserAccessException user no longer has any permission to use repository.
|
||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||
* @throws IOException if an IO error occurs
|
||||
* @see RemoteRepositoryHandle#getServerUserList()
|
||||
*/
|
||||
public String[] getServerUserList() throws IOException {
|
||||
|
@ -371,8 +379,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
* @param users list of user and access permissions.
|
||||
* @param anonymousAccessAllowed true to permit anonymous access (also requires anonymous
|
||||
* access to be enabled for server)
|
||||
* @throws UserAccessException
|
||||
* @throws IOException
|
||||
* @throws UserAccessException user is not a repository Admin
|
||||
* @throws IOException if an IO error occurs
|
||||
* @throws NotConnectedException if server/repository connection is down (user already informed)
|
||||
* @see RemoteRepositoryHandle#setUserList(User[], boolean)
|
||||
*/
|
||||
|
@ -392,7 +400,36 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* @see RepositoryHandle#createTextDataFile(String, String, String, String, String, String)
|
||||
*/
|
||||
public void createTextDataFile(String parentPath, String itemName, String fileID,
|
||||
String contentType, String textData, String comment)
|
||||
throws IOException, InvalidNameException {
|
||||
synchronized (serverAdapter) {
|
||||
checkRepository();
|
||||
try {
|
||||
repository.createTextDataFile(parentPath, itemName, fileID, contentType, textData,
|
||||
comment);
|
||||
}
|
||||
catch (NotConnectedException | RemoteException e) {
|
||||
checkUnmarshalException(e, "createTextDataFile");
|
||||
if (recoverConnection(e)) {
|
||||
try {
|
||||
repository.createTextDataFile(parentPath, itemName, fileID, contentType,
|
||||
textData, comment);
|
||||
}
|
||||
catch (RemoteException e1) {
|
||||
checkUnmarshalException(e1, "createTextDataFile");
|
||||
throw e1;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see RepositoryHandle#createDatabase(String, String, String, int, String, String)
|
||||
*/
|
||||
public ManagedBufferFileAdapter createDatabase(String parentPath, String itemName,
|
||||
|
@ -530,8 +567,8 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
|
||||
/**
|
||||
* Convert UnmarshalException into UnsupportedOperationException
|
||||
* @param e
|
||||
* @throws UnsupportedOperationException
|
||||
* @param e IOException to be converted if appropriate
|
||||
* @throws UnsupportedOperationException unsupported operation exception
|
||||
*/
|
||||
private void checkUnmarshalException(IOException e, String operation)
|
||||
throws UnsupportedOperationException {
|
||||
|
@ -931,5 +968,4 @@ public class RepositoryAdapter implements RemoteAdapterListener {
|
|||
public int getOpenFileHandleCount() {
|
||||
return openFileHandleCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -168,13 +168,14 @@ class ServerConnectTask extends Task {
|
|||
monitor.setCancelEnabled(false);
|
||||
monitor.setMessage("Connecting...");
|
||||
|
||||
Registry reg =
|
||||
LocateRegistry.getRegistry(server.getServerName(), server.getPortNumber(),
|
||||
new SslRMIClientSocketFactory());
|
||||
Registry reg = LocateRegistry.getRegistry(server.getServerName(),
|
||||
server.getPortNumber(), new SslRMIClientSocketFactory());
|
||||
checkServerBindNames(reg);
|
||||
|
||||
gsh = (GhidraServerHandle) reg.lookup(GhidraServerHandle.BIND_NAME);
|
||||
gsh.checkCompatibility(GhidraServerHandle.INTERFACE_VERSION);
|
||||
|
||||
// Check interface compatibility with the minimum supported version
|
||||
gsh.checkCompatibility(GhidraServerHandle.MINIMUM_INTERFACE_VERSION);
|
||||
}
|
||||
catch (NotBoundException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
|
@ -237,8 +238,7 @@ class ServerConnectTask extends Task {
|
|||
* @throws LoginException login failure
|
||||
*/
|
||||
private RemoteRepositoryServerHandle getRepositoryServerHandle(String defaultUserID,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, LoginException, CancelledException {
|
||||
TaskMonitor monitor) throws IOException, LoginException, CancelledException {
|
||||
|
||||
GhidraServerHandle gsh = getGhidraServerHandle(server, monitor);
|
||||
|
||||
|
@ -296,7 +296,8 @@ class ServerConnectTask extends Task {
|
|||
"Client PKI certificate has not been installed");
|
||||
}
|
||||
|
||||
if (ApplicationKeyManagerFactory.usingGeneratedSelfSignedCertificate()) {
|
||||
if (ApplicationKeyManagerFactory
|
||||
.usingGeneratedSelfSignedCertificate()) {
|
||||
Msg.warn(this,
|
||||
"Server connect - client is using self-signed PKI certificate");
|
||||
}
|
||||
|
|
|
@ -50,8 +50,20 @@ public interface GhidraServerHandle extends Remote {
|
|||
* - version 9.1 switched to using SSL/TLS for RMI registry connection preventing
|
||||
* older clients the ability to connect to the server. Remote interface remained
|
||||
* unchanged allowing 9.1 clients to connect to 9.0 server.
|
||||
* 12: Revised RepositoryFile serialization to facilitate support for text-data used
|
||||
* for link-file storage.
|
||||
*/
|
||||
public static final int INTERFACE_VERSION = 11;
|
||||
|
||||
/**
|
||||
* The server interface version that the server will use and is the maximum version that the
|
||||
* client can operate with.
|
||||
*/
|
||||
public static final int INTERFACE_VERSION = 12;
|
||||
|
||||
/**
|
||||
* The minimum server interface version that the client can operate with.
|
||||
*/
|
||||
public static final int MINIMUM_INTERFACE_VERSION = 11;
|
||||
|
||||
/**
|
||||
* Minimum version of Ghidra which utilized the current INTERFACE_VERSION
|
||||
|
|
|
@ -70,6 +70,10 @@ public interface RemoteRepositoryHandle extends RepositoryHandle, Remote {
|
|||
int bufferSize, String contentType, String projectPath)
|
||||
throws IOException, InvalidNameException;
|
||||
|
||||
@Override
|
||||
void createTextDataFile(String parentPath, String itemName, String fileID, String contentType,
|
||||
String textData, String comment) throws InvalidNameException, IOException;
|
||||
|
||||
@Override
|
||||
ManagedBufferFileHandle openDatabase(String parentPath, String itemName, int version,
|
||||
int minChangeDataVer) throws IOException;
|
||||
|
|
|
@ -123,6 +123,21 @@ public interface RepositoryHandle {
|
|||
*/
|
||||
RepositoryItem getItem(String fileID) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a new text data file within the specified parent folder.
|
||||
* @param parentPath folder path of parent
|
||||
* @param itemName new data file name
|
||||
* @param fileID unique file ID
|
||||
* @param contentType application defined content type
|
||||
* @param textData text data (required)
|
||||
* @param comment file comment (may be null)
|
||||
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||
* @throws InvalidNameException if the name has illegal characters.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
void createTextDataFile(String parentPath, String itemName, String fileID, String contentType,
|
||||
String textData, String comment) throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Create a new empty database item within the repository.
|
||||
* @param parentPath parent folder path
|
||||
|
@ -138,8 +153,8 @@ public interface RepositoryHandle {
|
|||
* @throws InvalidNameException if itemName or parentPath contains invalid characters
|
||||
*/
|
||||
ManagedBufferFileHandle createDatabase(String parentPath, String itemName, String fileID,
|
||||
int bufferSize, String contentType, String projectPath) throws IOException,
|
||||
InvalidNameException;
|
||||
int bufferSize, String contentType, String projectPath)
|
||||
throws IOException, InvalidNameException;
|
||||
|
||||
/**
|
||||
* Open an existing version of a database buffer file for non-update read-only use.
|
||||
|
@ -212,8 +227,8 @@ public interface RepositoryHandle {
|
|||
* @throws DuplicateFileException if target item already exists
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
void moveItem(String oldParentPath, String newParentPath, String oldItemName, String newItemName)
|
||||
throws InvalidNameException, IOException;
|
||||
void moveItem(String oldParentPath, String newParentPath, String oldItemName,
|
||||
String newItemName) throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Perform a checkout on the specified item.
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
*/
|
||||
package ghidra.framework.remote;
|
||||
|
||||
import ghidra.framework.store.FileSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InvalidClassException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.store.FileSystem;
|
||||
|
||||
/**
|
||||
* <code>RepositoryItemStatus</code> provides status information for a
|
||||
|
@ -25,18 +28,36 @@ import java.io.IOException;
|
|||
*/
|
||||
public class RepositoryItem implements java.io.Serializable {
|
||||
|
||||
// Serial version 2 supports an expandable schema which allows a newer repository server
|
||||
// to remain usable by older clients, and a newer client to deserialize data from an older
|
||||
// server. The optional schema version if present can be used to identify the additional
|
||||
// serialized data which may following the schema version number.
|
||||
|
||||
public final static long serialVersionUID = 2L;
|
||||
|
||||
public final static int FILE = 1;
|
||||
public final static int DATABASE = 2;
|
||||
private static final byte SERIALIZATION_SCHEMA_VERSION = 1;
|
||||
|
||||
protected String folderPath;
|
||||
protected String itemName;
|
||||
protected String fileID;
|
||||
protected int itemType;
|
||||
protected String contentType;
|
||||
protected int version;
|
||||
protected long versionTime;
|
||||
public final static int FILE = 1; // DataFileItem (not yet supported)
|
||||
public final static int DATABASE = 2; // DatabaseItem
|
||||
public final static int TEXT_DATA_FILE = 3; // TextDataItem
|
||||
|
||||
//
|
||||
// Client use can support reading from older server which presents serialVersionUID==2
|
||||
//
|
||||
|
||||
private String folderPath;
|
||||
private String itemName;
|
||||
private String fileID;
|
||||
private int itemType;
|
||||
private String contentType;
|
||||
private int version;
|
||||
private long versionTime;
|
||||
|
||||
// Variables below were added after serialVersionUID == 2 was established and rely on
|
||||
// additional serialization version byte to identify the optional data fields added
|
||||
// after original serialVersionUID == 2 fields.
|
||||
|
||||
private String textData; // applies to TEXT_DATA_FILE introduced with GhidraServerHandle v12
|
||||
|
||||
/**
|
||||
* Default constructor needed for de-serialization
|
||||
|
@ -53,9 +74,10 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
* @param contentType content type associated with item
|
||||
* @param version repository item version or -1 if versioning not supported
|
||||
* @param versionTime version creation time
|
||||
* @param textData related text data (may be null)
|
||||
*/
|
||||
public RepositoryItem(String folderPath, String itemName, String fileID, int itemType,
|
||||
String contentType, int version, long versionTime) {
|
||||
String contentType, int version, long versionTime, String textData) {
|
||||
this.folderPath = folderPath;
|
||||
this.itemName = itemName;
|
||||
this.fileID = fileID;
|
||||
|
@ -63,6 +85,7 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
this.contentType = contentType;
|
||||
this.version = version;
|
||||
this.versionTime = versionTime;
|
||||
this.textData = textData;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,6 +94,7 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
|
||||
|
||||
out.writeLong(serialVersionUID);
|
||||
out.writeUTF(folderPath);
|
||||
out.writeUTF(itemName);
|
||||
|
@ -79,6 +103,12 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
out.writeUTF(contentType != null ? contentType : "");
|
||||
out.writeInt(version);
|
||||
out.writeLong(versionTime);
|
||||
|
||||
// Variables below were added after serialVersionUID == 2 was established
|
||||
|
||||
out.writeByte(SERIALIZATION_SCHEMA_VERSION);
|
||||
out.writeUTF(textData != null ? textData : "");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,11 +117,11 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
* @throws IOException if IO error occurs
|
||||
* @throws ClassNotFoundException if unrecognized serialVersionUID detected
|
||||
*/
|
||||
private void readObject(java.io.ObjectInputStream in) throws IOException,
|
||||
ClassNotFoundException {
|
||||
private void readObject(java.io.ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException {
|
||||
long serialVersion = in.readLong();
|
||||
if (serialVersion != serialVersionUID) {
|
||||
throw new ClassNotFoundException("Unsupported version of RepositoryItemStatus");
|
||||
throw new ClassNotFoundException("Unsupported version of RepositoryItem");
|
||||
}
|
||||
folderPath = in.readUTF();
|
||||
itemName = in.readUTF();
|
||||
|
@ -106,6 +136,31 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
}
|
||||
version = in.readInt();
|
||||
versionTime = in.readLong();
|
||||
|
||||
// Variable handling below was added after serialVersionUID == 2 was established
|
||||
|
||||
int available = in.available();
|
||||
if (available == 0) {
|
||||
// assume original schema before serializationSchemaVersion was employed
|
||||
return;
|
||||
}
|
||||
|
||||
// Since we do not serialize class implementations with RMI the older client must be able to
|
||||
// read the initial data sequence that was previously supported. Newer clients that have this
|
||||
// class will use the presence of the version byte to handle communicating with either an
|
||||
// older server (no version byte) or a newer server (version byte and subsequent data is read)
|
||||
byte serializationSchemaVersion = in.readByte();
|
||||
if (serializationSchemaVersion < 1 ||
|
||||
serializationSchemaVersion > SERIALIZATION_SCHEMA_VERSION) {
|
||||
throw new InvalidClassException("RepositoryItem",
|
||||
"RepositoryItem has incompatible serialization schema version: " +
|
||||
serializationSchemaVersion);
|
||||
}
|
||||
|
||||
textData = in.readUTF();
|
||||
if (StringUtils.isBlank(textData)) {
|
||||
textData = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,4 +217,11 @@ public class RepositoryItem implements java.io.Serializable {
|
|||
return versionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get related text data
|
||||
* @return text data or null
|
||||
*/
|
||||
public String getTextData() {
|
||||
return textData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -24,7 +23,7 @@ import java.io.OutputStream;
|
|||
* <code>DataFileItem</code> corresponds to a private serialized
|
||||
* data file within a FileSystem. Methods are provided for opening
|
||||
* the underlying file as an input or output stream.
|
||||
* <br>
|
||||
* <P>
|
||||
* NOTE: The use of DataFile is not encouraged and is not fully
|
||||
* supported.
|
||||
*/
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
package ghidra.framework.store;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import db.buffers.BufferFile;
|
||||
import db.buffers.ManagedBufferFile;
|
||||
import ghidra.framework.store.local.UnknownFolderItem;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.remote.RemoteFileSystem;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -40,38 +43,38 @@ public interface FileSystem {
|
|||
* Get user name associated with this filesystem. In the case of a remote filesystem
|
||||
* this will correspond to the name used during login/authentication. A null value may
|
||||
* be returned if user name unknown.
|
||||
* @return user name used to authenticate or null if not-applicable
|
||||
*/
|
||||
String getUserName();
|
||||
public String getUserName();
|
||||
|
||||
/**
|
||||
* Returns true if the file-system requires check-outs when
|
||||
* modifying folder items.
|
||||
* {@return true if the file-system requires check-outs when
|
||||
* modifying folder items.}
|
||||
*/
|
||||
public boolean isVersioned();
|
||||
|
||||
/**
|
||||
* Returns true if file-system is on-line.
|
||||
* {@return true if file-system is on-line.}
|
||||
*/
|
||||
public boolean isOnline();
|
||||
|
||||
/**
|
||||
* Returns true if file-system is read-only.
|
||||
* @throws IOException
|
||||
* {@return true if file-system is read-only.}
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
public boolean isReadOnly() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the number of folder items contained within this file-system.
|
||||
* @throws IOException
|
||||
* {@return the number of folder items contained within this file-system.}
|
||||
* @throws IOException if an IO error occurs
|
||||
* @throws UnsupportedOperationException if file-system does not support this operation
|
||||
*/
|
||||
public int getItemCount() throws IOException, UnsupportedOperationException;
|
||||
|
||||
/**
|
||||
* Returns a list of the folder item names contained in the given folder.
|
||||
* {@return a list of the folder item names contained in the given folder.}
|
||||
* @param folderPath the path of the folder.
|
||||
* @return a list of folder item names.
|
||||
* @throws IOException
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public String[] getItemNames(String folderPath) throws IOException;
|
||||
|
||||
|
@ -81,7 +84,7 @@ public interface FileSystem {
|
|||
* @return a list of folder items. Null items may exist if index contained item name
|
||||
* while storage was not found. An {@link UnknownFolderItem} may be returned if unsupported
|
||||
* item storage encountered.
|
||||
* @throws IOException
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public FolderItem[] getItems(String folderPath) throws IOException;
|
||||
|
||||
|
@ -105,6 +108,8 @@ public interface FileSystem {
|
|||
|
||||
/**
|
||||
* Return a list of subfolders (by name) that are stored within the specified folder path.
|
||||
* @param folderPath folder path
|
||||
* @return subfolders names
|
||||
* @throws FileNotFoundException if folder path does not exist.
|
||||
* @throws IOException if IO error occurs.
|
||||
*/
|
||||
|
@ -122,6 +127,16 @@ public interface FileSystem {
|
|||
public void createFolder(String parentPath, String folderName)
|
||||
throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Determine if the specified folder item is supported by this filesystem's interface and
|
||||
* storage. This method primarily exists to determine if a remote server can support
|
||||
* the specified content. This can come into play as new storage formats are added
|
||||
* to a {@link LocalFileSystem} but may not be supported by a connected {@link RemoteFileSystem}.
|
||||
* @param folderItem folder item
|
||||
* @return true if folder item storage is supported
|
||||
*/
|
||||
public boolean isSupportedItemType(FolderItem folderItem);
|
||||
|
||||
/**
|
||||
* Create a new database item within the specified parent folder using the contents
|
||||
* of the specified BufferFile.
|
||||
|
@ -162,8 +177,7 @@ public interface FileSystem {
|
|||
* @return an empty BufferFile open for read-write.
|
||||
* @throws FileNotFoundException thrown if parent folder does not exist.
|
||||
* @throws DuplicateFileException if a folder item exists with this name
|
||||
* @throws InvalidNameException if the name does not have
|
||||
* all alphanumerics
|
||||
* @throws InvalidNameException if the name has illegal characters.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public ManagedBufferFile createDatabase(String parentPath, String name, String fileID,
|
||||
|
@ -182,7 +196,6 @@ public interface FileSystem {
|
|||
* @return new data file
|
||||
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||
* @throws InvalidNameException if the name has illegal characters.
|
||||
* all alphanumerics
|
||||
* @throws IOException if an IO error occurs.
|
||||
* @throws CancelledException if cancelled by monitor
|
||||
*/
|
||||
|
@ -190,6 +203,23 @@ public interface FileSystem {
|
|||
String comment, String contentType, TaskMonitor monitor)
|
||||
throws InvalidNameException, IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Creates a new text data file within the specified parent folder.
|
||||
* @param parentPath folder path of parent
|
||||
* @param name new data file name
|
||||
* @param fileID file ID to be associated with new file or null
|
||||
* @param contentType application defined content type
|
||||
* @param textData text data (required)
|
||||
* @param comment file comment (may be null, only used if versioning is enabled)
|
||||
* @return new data file
|
||||
* @throws DuplicateFileException Thrown if a folderItem with that name already exists.
|
||||
* @throws InvalidNameException if the name has illegal characters.
|
||||
* @throws IOException if an IO error occurs.
|
||||
*/
|
||||
public TextDataItem createTextDataItem(String parentPath, String name, String fileID,
|
||||
String contentType, String textData, String comment)
|
||||
throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Creates a new file item from a packed file.
|
||||
* The content/item type must be determined from the input stream.
|
||||
|
@ -252,7 +282,8 @@ public interface FileSystem {
|
|||
* Moves the specified item to a new folder.
|
||||
* @param folderPath path of folder containing the item.
|
||||
* @param name name of the item to be moved.
|
||||
* @param newFolderPath path of folder where item is to be moved.
|
||||
* @param newFolderPath path of folder where item is to be moved to.
|
||||
* @param newName new item name to be applied
|
||||
* @throws FileNotFoundException if the item does not exist.
|
||||
* @throws DuplicateFileException if item with the same name exists within the new parent folder.
|
||||
* @throws FileInUseException if the item is in-use or checked-out
|
||||
|
@ -263,14 +294,14 @@ public interface FileSystem {
|
|||
throws IOException, InvalidNameException;
|
||||
|
||||
/**
|
||||
* Adds the given listener to be notified of file system changes.
|
||||
* Adds a file system listener to be notified of file system changes.
|
||||
* @param listener the listener to be added.
|
||||
*/
|
||||
public void addFileSystemListener(FileSystemListener listener);
|
||||
|
||||
/**
|
||||
* Removes the listener from being notified of file system changes.
|
||||
* @param listener
|
||||
* Removes a file system listener from being notified of file system changes.
|
||||
* @param listener file system listener
|
||||
*/
|
||||
public void removeFileSystemListener(FileSystemListener listener);
|
||||
|
||||
|
@ -283,7 +314,7 @@ public interface FileSystem {
|
|||
public boolean folderExists(String folderPath) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if the file exists
|
||||
* {@return true if the file exists}
|
||||
* @param folderPath the folderPath of the folder that may contain the file.
|
||||
* @param name the name of the file to check for existence.
|
||||
* @throws IOException if an IO error occurs.
|
||||
|
@ -291,7 +322,7 @@ public interface FileSystem {
|
|||
public boolean fileExists(String folderPath, String name) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if this file system is shared
|
||||
* {@return true if this file system is shared}
|
||||
*/
|
||||
public boolean isShared();
|
||||
|
||||
|
@ -300,4 +331,58 @@ public interface FileSystem {
|
|||
*/
|
||||
public void dispose();
|
||||
|
||||
/**
|
||||
* Normalize an absolute path, removing all "." and ".." use.
|
||||
* <P>
|
||||
* NOTE: This method does not consider possible linked folder traversal which may
|
||||
* get ignored when flattening/simplifying path.
|
||||
*
|
||||
* @param path absolute filesystem path which may contain "." or ".." path elements.
|
||||
* @return normalized path
|
||||
* @throws IllegalArgumentException if an absolute path starting with {@link #SEPARATOR}
|
||||
* was not specified or an illegal path was specified.
|
||||
*/
|
||||
public static String normalizePath(String path) throws IllegalArgumentException {
|
||||
if (!path.startsWith(SEPARATOR)) {
|
||||
throw new IllegalArgumentException("Absolute path required");
|
||||
}
|
||||
|
||||
String[] split = path.split(SEPARATOR);
|
||||
|
||||
ArrayList<String> elements = new ArrayList<>();
|
||||
for (int i = 1; i < split.length; i++) {
|
||||
String e = split[i];
|
||||
if (e.length() == 0) {
|
||||
throw new IllegalArgumentException("Invalid path with empty element: " + path);
|
||||
}
|
||||
if ("..".equals(e)) {
|
||||
try {
|
||||
// remove last element
|
||||
elements.removeLast();
|
||||
}
|
||||
catch (NoSuchElementException ex) {
|
||||
throw new IllegalArgumentException("Invalid path: " + path);
|
||||
}
|
||||
}
|
||||
else if (".".equals(e)) {
|
||||
// ignore element
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
elements.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
return SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String e : elements) {
|
||||
buf.append(SEPARATOR);
|
||||
buf.append(e);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@ public interface FolderItem {
|
|||
*/
|
||||
public static final int DATAFILE_FILE_TYPE = 1;
|
||||
|
||||
/**
|
||||
* Item type is associated with metadata only (e.g., URL)
|
||||
*/
|
||||
public static final int LINK_FILE_TYPE = 2;
|
||||
|
||||
/**
|
||||
* Default checkout ID used when a checkout is not applicable.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.store;
|
||||
|
||||
/**
|
||||
* <code>TextDataItem</code> corresponds to a file which contains text data only
|
||||
* and relies only on property file storage (i.e., no separate database or data file).
|
||||
*/
|
||||
public interface TextDataItem extends FolderItem {
|
||||
|
||||
/**
|
||||
* Get the text data that was stored with this item
|
||||
* @return text data
|
||||
*/
|
||||
public String getTextData();
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.store;
|
||||
|
||||
/**
|
||||
* <code>UnknownFolderItem</code> corresponds to a folder item which has an unknown storage type
|
||||
* or has encountered a storage failure.
|
||||
*/
|
||||
public interface UnknownFolderItem extends FolderItem {
|
||||
|
||||
public static final String UNKNOWN_CONTENT_TYPE = "Unknown-File";
|
||||
|
||||
/**
|
||||
* Get the file type:
|
||||
* <ul>
|
||||
* <li>{@link FolderItem#DATABASE_FILE_TYPE}</li>
|
||||
* <li>{@link FolderItem#DATAFILE_FILE_TYPE}</li>
|
||||
* <li>{@link FolderItem#LINK_FILE_TYPE}</li>
|
||||
* </ul>
|
||||
* @return file type or {@link FolderItem#UNKNOWN_FILE_TYPE} (-1) if unknown
|
||||
*/
|
||||
public int getFileType();
|
||||
|
||||
}
|
|
@ -39,7 +39,7 @@ import utilities.util.FileUtilities;
|
|||
/**
|
||||
* <code>PackedDatabase</code> provides a packed form of Database
|
||||
* which compresses a single version into a file.
|
||||
* <br>
|
||||
* <P>
|
||||
* When opening a packed database, a PackedDBHandle is returned
|
||||
* after first expanding the file into a temporary Database.
|
||||
*/
|
||||
|
@ -276,8 +276,8 @@ public class PackedDatabase extends Database {
|
|||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if unpack/open is cancelled
|
||||
*/
|
||||
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile, boolean neverCache,
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
public static synchronized PackedDatabase getPackedDatabase(ResourceFile packedDbFile,
|
||||
boolean neverCache, TaskMonitor monitor) throws IOException, CancelledException {
|
||||
if (!neverCache && PackedDatabaseCache.isEnabled()) {
|
||||
try {
|
||||
return PackedDatabaseCache.getCache().getCachedDB(packedDbFile, monitor);
|
||||
|
@ -633,7 +633,7 @@ public class PackedDatabase extends Database {
|
|||
tmpFile = Application.createTempFile("pack", ".tmp");
|
||||
tmpFile.delete();
|
||||
dbh.saveAs(tmpFile, false, monitor);
|
||||
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))){
|
||||
try (InputStream itemIn = new BufferedInputStream(new FileInputStream(tmpFile))) {
|
||||
ItemSerializer.outputItem(itemName, contentType, FolderItem.DATABASE_FILE_TYPE,
|
||||
tmpFile.length(), itemIn, outputFile, monitor);
|
||||
}
|
||||
|
|
|
@ -598,7 +598,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean addFileToIndex(PropertyFile pfile) throws IOException, NotFoundException {
|
||||
private boolean addFileToIndex(ItemPropertyFile pfile) throws IOException, NotFoundException {
|
||||
|
||||
String parentPath = pfile.getParentPath();
|
||||
String name = pfile.getName();
|
||||
|
@ -832,7 +832,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
catch (NotFoundException e) {
|
||||
// ignore - handled below
|
||||
}
|
||||
throw new FileNotFoundException("Item not found: " + folderPath + SEPARATOR + itemName);
|
||||
throw new FileNotFoundException("Item not found: " + getPath(folderPath, itemName));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1207,7 +1207,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
String newFolderPath = folder.getPathname();
|
||||
for (Item item : folder.items.values()) {
|
||||
ItemStorage itemStorage = item.itemStorage;
|
||||
PropertyFile pfile = item.itemStorage.getPropertyFile();
|
||||
ItemPropertyFile pfile = item.itemStorage.getPropertyFile();
|
||||
pfile.moveTo(itemStorage.dir, itemStorage.storageName, newFolderPath,
|
||||
itemStorage.itemName);
|
||||
itemStorage.folderPath = newFolderPath;
|
||||
|
@ -1236,7 +1236,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
folder = getFolder(folderPath, GetFolderOption.READ_ONLY);
|
||||
if (folder.parent.folders.get(newFolderName) != null) {
|
||||
throw new DuplicateFileException(
|
||||
parentPath + SEPARATOR + newFolderName + " already exists.");
|
||||
getPath(parentPath, newFolderName) + " already exists.");
|
||||
}
|
||||
|
||||
indexJournal.moveFolder(folderPath, getPath(parentPath, newFolderName));
|
||||
|
@ -1462,7 +1462,6 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
}
|
||||
|
||||
private void replayJournal() throws IndexReadException {
|
||||
Msg.info(this, "restoring data storage index...");
|
||||
int lineNum = 0;
|
||||
BufferedReader journalReader = null;
|
||||
try {
|
||||
|
@ -1778,7 +1777,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
|
|||
}
|
||||
|
||||
@Override
|
||||
PropertyFile getPropertyFile() throws IOException {
|
||||
ItemPropertyFile getPropertyFile() throws IOException {
|
||||
return new IndexedPropertyFile(dir, storageName, folderPath, itemName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -16,59 +15,68 @@
|
|||
*/
|
||||
package ghidra.framework.store.local;
|
||||
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.util.PropertyFile;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class IndexedPropertyFile extends PropertyFile {
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
|
||||
public final static String NAME_PROPERTY = "NAME";
|
||||
public final static String PARENT_PATH_PROPERTY = "PARENT";
|
||||
public class IndexedPropertyFile extends ItemPropertyFile {
|
||||
|
||||
protected static final String NAME_PROPERTY = "NAME";
|
||||
protected static final String PARENT_PATH_PROPERTY = "PARENT";
|
||||
|
||||
/**
|
||||
* Construct a new or existing PropertyFile.
|
||||
* This form ignores retained property values for NAME and PARENT path.
|
||||
* This constructor ignores retained property values for NAME and PARENT path.
|
||||
* This constructor will not throw an exception if the file does not exist.
|
||||
* @param dir parent directory
|
||||
* @param storageName stored property file name (without extension)
|
||||
* @param parentPath path to parent
|
||||
* @param name name of the property file
|
||||
* @throws IOException
|
||||
* @throws InvalidObjectException if a file parse error occurs
|
||||
* @throws IOException if an IO error occurs reading an existing file
|
||||
*/
|
||||
public IndexedPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||
throws IOException {
|
||||
super(dir, storageName, parentPath, name);
|
||||
// if (exists() &&
|
||||
// (!name.equals(getString(NAME_PROPERTY, null)) || !parentPath.equals(getString(
|
||||
// PARENT_PATH_PROPERTY, null)))) {
|
||||
// throw new AssertException();
|
||||
// }
|
||||
if (contains(NAME_PROPERTY) && contains(PARENT_PATH_PROPERTY)) {
|
||||
this.name = getString(NAME_PROPERTY, name);
|
||||
this.parentPath = getString(PARENT_PATH_PROPERTY, parentPath);
|
||||
}
|
||||
else {
|
||||
// new property file
|
||||
putString(NAME_PROPERTY, name);
|
||||
putString(PARENT_PATH_PROPERTY, parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an existing PropertyFile.
|
||||
* Construct a existing PropertyFile.
|
||||
* This constructor uses property values for NAME and PARENT path.
|
||||
* @param dir parent directory
|
||||
* @param storageName stored property file name (without extension)
|
||||
* @throws FileNotFoundException if property file does not exist
|
||||
* @throws InvalidObjectException if a file parse error occurs
|
||||
* @throws IOException if error occurs reading property file
|
||||
*/
|
||||
public IndexedPropertyFile(File dir, String storageName) throws IOException {
|
||||
super(dir, storageName, FileSystem.SEPARATOR, storageName);
|
||||
super(dir, storageName, null, null);
|
||||
if (!exists()) {
|
||||
throw new FileNotFoundException();
|
||||
throw new FileNotFoundException(
|
||||
new File(dir, storageName + PROPERTY_EXT) + " not found");
|
||||
}
|
||||
name = getString(NAME_PROPERTY, null);
|
||||
parentPath = getString(PARENT_PATH_PROPERTY, null);
|
||||
if (name == null || parentPath == null) {
|
||||
throw new IOException("Invalid indexed property file: " + propertyFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an existing PropertyFile.
|
||||
* @param file
|
||||
* Construct a existing PropertyFile.
|
||||
* This constructor uses property values for NAME and PARENT path.
|
||||
* @param file property file
|
||||
* @throws FileNotFoundException if property file does not exist
|
||||
* @throws InvalidObjectException if a file parse error occurs
|
||||
* @throws IOException if error occurs reading property file
|
||||
*/
|
||||
public IndexedPropertyFile(File file) throws IOException {
|
||||
|
@ -82,27 +90,17 @@ public class IndexedPropertyFile extends PropertyFile {
|
|||
return propertyFileName.substring(0, propertyFileName.length() - PROPERTY_EXT.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readState() throws IOException {
|
||||
super.readState();
|
||||
name = getString(NAME_PROPERTY, null);
|
||||
parentPath = getString(PARENT_PATH_PROPERTY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(File newParent, String newStorageName, String newParentPath, String newName)
|
||||
throws DuplicateFileException, IOException {
|
||||
|
||||
String oldName = name;
|
||||
String oldParentPath = parentPath;
|
||||
super.moveTo(newParent, newStorageName, newParentPath, newName);
|
||||
// if (!parentPath.equals(newParentPath)) {
|
||||
// throw new AssertException();
|
||||
// }
|
||||
// if (!name.equals(newName)) {
|
||||
// throw new AssertException();
|
||||
// }
|
||||
putString(NAME_PROPERTY, newName);
|
||||
putString(PARENT_PATH_PROPERTY, newParentPath);
|
||||
if (!newParentPath.equals(oldParentPath) || !newName.equals(oldName)) {
|
||||
putString(NAME_PROPERTY, name);
|
||||
putString(PARENT_PATH_PROPERTY, parentPath);
|
||||
writeState();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import java.io.*;
|
|||
import java.util.HashMap;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.PropertyFile;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
|
||||
/**
|
||||
|
@ -94,7 +93,7 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void fileIdChanged(PropertyFile pfile, String oldFileId)
|
||||
protected synchronized void fileIdChanged(ItemPropertyFile pfile, String oldFileId)
|
||||
throws IOException {
|
||||
indexJournal.open();
|
||||
try {
|
||||
|
@ -143,12 +142,19 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
|
|||
if (item == null) {
|
||||
return null;
|
||||
}
|
||||
ItemStorage itemStorage = item.itemStorage;
|
||||
try {
|
||||
PropertyFile propertyFile = item.itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
if (propertyFile.exists()) {
|
||||
return LocalFolderItem.getFolderItem(this, propertyFile);
|
||||
}
|
||||
}
|
||||
catch (InvalidObjectException e) {
|
||||
// Use unknown placeholder item on failure
|
||||
InvalidPropertyFile invalidFile = new InvalidPropertyFile(itemStorage.dir,
|
||||
itemStorage.storageName, itemStorage.folderPath, itemStorage.itemName);
|
||||
return new LocalUnknownFolderItem(this, invalidFile);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.store.local;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link InvalidPropertyFile} provides a substitue {@link ItemPropertyFile} when one
|
||||
* fails to parse. This allows the item's existance to be managed even if the item cannot
|
||||
* be opened.
|
||||
*/
|
||||
public class InvalidPropertyFile extends ItemPropertyFile {
|
||||
|
||||
/**
|
||||
* Construct an invalid property file instance if it previously failed to parse.
|
||||
* @param dir native directory where this file is stored
|
||||
* @param storageName stored property file name (without extension)
|
||||
* @param parentPath logical parent path for the associated item
|
||||
* @param name name of the associated item
|
||||
* @throws IOException (never thrown since file is never read)
|
||||
*/
|
||||
public InvalidPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||
throws IOException {
|
||||
super(dir, storageName, parentPath, name);
|
||||
// NOTE: IOException is prevented by having a do-nothing readState method below
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void readState() {
|
||||
// avoid potential parse failure
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.store.local;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.FolderItem;
|
||||
import ghidra.util.PropertyFile;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
|
||||
/**
|
||||
* {@link ItemPropertyFile} provides basic property storage which is primarily intended to
|
||||
* store limited information related to a logical {@link FolderItem}. The file
|
||||
* extension used is {@link #PROPERTY_EXT}.
|
||||
*/
|
||||
public class ItemPropertyFile extends PropertyFile {
|
||||
|
||||
private static final String FILE_ID_PROPERTY = "FILE_ID";
|
||||
|
||||
protected String name;
|
||||
protected String parentPath;
|
||||
|
||||
/**
|
||||
* Construct a new or existing PropertyFile.
|
||||
* This constructor ignores retained property values for NAME and PARENT path.
|
||||
* This constructor will not throw an exception if the file does not exist.
|
||||
* @param dir native directory where this file is stored
|
||||
* @param storageName stored property file name (without extension)
|
||||
* @param parentPath logical parent path for the associated item
|
||||
* @param name name of the associated item
|
||||
* @throws InvalidObjectException if a file parse error occurs
|
||||
* @throws IOException if an IO error occurs reading an existing file
|
||||
*/
|
||||
public ItemPropertyFile(File dir, String storageName, String parentPath, String name)
|
||||
throws IOException {
|
||||
super(dir, storageName);
|
||||
this.name = name;
|
||||
this.parentPath = parentPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the item associated with this PropertyFile. A null value may be returned
|
||||
* if this is an older property file and the name was not specified at
|
||||
* time of construction.
|
||||
* @return associated item name or null if unknown
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logical path of the item associated with this PropertyFile. A null value may be
|
||||
* returned if this is an older property file and the name and parentPath was not specified at
|
||||
* time of construction.
|
||||
* @return logical path of the associated item or null if unknown
|
||||
*/
|
||||
public String getPath() {
|
||||
if (parentPath == null || name == null) {
|
||||
return null;
|
||||
}
|
||||
if (parentPath.length() == 1) {
|
||||
return parentPath + name;
|
||||
}
|
||||
return parentPath + FileSystem.SEPARATOR_CHAR + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the logical parent path containing the item descibed by this PropertyFile.
|
||||
* @return logical parent directory path
|
||||
*/
|
||||
public String getParentPath() {
|
||||
return parentPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FileID associated with this file.
|
||||
* @return FileID associated with this file or null
|
||||
*/
|
||||
public String getFileID() {
|
||||
return getString(FILE_ID_PROPERTY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the FileID associated with this file.
|
||||
* @param fileId unique file ID
|
||||
*/
|
||||
public void setFileID(String fileId) {
|
||||
putString(FILE_ID_PROPERTY, fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this PropertyFile to the newParent file.
|
||||
* @param newStorageParent new storage parent of the native file
|
||||
* @param newStorageName new storage name for this property file
|
||||
* @param newParentPath new logical parent path
|
||||
* @param newName new logical item name
|
||||
* @throws IOException thrown if there was a problem accessing the
|
||||
* @throws DuplicateFileException thrown if a file with the newName
|
||||
* already exists
|
||||
*/
|
||||
public void moveTo(File newStorageParent, String newStorageName, String newParentPath,
|
||||
String newName) throws DuplicateFileException, IOException {
|
||||
super.moveTo(newStorageParent, newStorageName);
|
||||
if (!newParentPath.equals(parentPath) || !newName.equals(name)) {
|
||||
parentPath = newParentPath;
|
||||
name = newName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE!! This method must not be used.
|
||||
* <P>
|
||||
* Movement of an item is related to its logical pathname and must be accomplished
|
||||
* with the {@link #moveTo(File, String, String, String)} method. There is no supported
|
||||
* direct use of this method.
|
||||
*
|
||||
* @param newStorageParent new storage parent of the native file
|
||||
* @param newStorageName new storage name for this property file
|
||||
* @throws UnsupportedOperationException always thrown
|
||||
* @deprecated method must not be used
|
||||
*/
|
||||
@Deprecated(forRemoval = false, since = "11.4")
|
||||
@Override
|
||||
public final void moveTo(File newStorageParent, String newStorageName)
|
||||
throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,27 +17,38 @@ package ghidra.framework.store.local;
|
|||
|
||||
import ghidra.framework.store.DataFileItem;
|
||||
import ghidra.framework.store.FolderItem;
|
||||
import ghidra.util.PropertyFile;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* <code>LocalDataFile</code> provides a FolderItem implementation
|
||||
* <code>LocalDataFileItem</code> provides a FolderItem implementation
|
||||
* for a local serialized data file. This implementation supports
|
||||
* a non-versioned file-system only.
|
||||
* <p>
|
||||
* This item utilizes a data directory for storing the serialized
|
||||
* data file.
|
||||
* <p>
|
||||
* NOTE: The use of this file item type is not fully supported.
|
||||
*/
|
||||
public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
||||
public class LocalDataFileItem extends LocalFolderItem implements DataFileItem {
|
||||
|
||||
private final static int IO_BUFFER_SIZE = 32 * 1024;
|
||||
private static final String DATA_FILE = "data.1.gdf";
|
||||
|
||||
public LocalDataFile(LocalFileSystem fileSystem, PropertyFile propertyFile) throws IOException {
|
||||
/**
|
||||
* Constructor for an existing local serialized=data file item which corresponds to the specified
|
||||
* property file.
|
||||
* @param fileSystem file system
|
||||
* @param propertyFile database property file
|
||||
* @throws IOException if an IO Error occurs
|
||||
*/
|
||||
public LocalDataFileItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||
throws IOException {
|
||||
super(fileSystem, propertyFile, true, false);
|
||||
|
||||
if (fileSystem.isVersioned()) {
|
||||
|
@ -50,7 +61,7 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a new local data file item.
|
||||
* Create a new local serialized-data file item.
|
||||
* @param fileSystem file system
|
||||
* @param propertyFile serialized data property file
|
||||
* @param istream data source input stream (should be a start of data and will be read to end of file).
|
||||
|
@ -61,9 +72,9 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
|||
* @throws IOException if an IO Error occurs
|
||||
* @throws CancelledException if monitor cancels operation
|
||||
*/
|
||||
public LocalDataFile(LocalFileSystem fileSystem, PropertyFile propertyFile,
|
||||
InputStream istream, String contentType, TaskMonitor monitor) throws IOException,
|
||||
CancelledException {
|
||||
public LocalDataFileItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||
InputStream istream, String contentType, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
super(fileSystem, propertyFile, true, true);
|
||||
|
||||
if (fileSystem.isVersioned()) {
|
||||
|
@ -71,6 +82,11 @@ public class LocalDataFile extends LocalFolderItem implements DataFileItem {
|
|||
throw new UnsupportedOperationException("Versioning not yet supported for DataFiles");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(contentType)) {
|
||||
abortCreate();
|
||||
throw new IllegalArgumentException("Missing content-type");
|
||||
}
|
||||
|
||||
File dataFile = getDataFile();
|
||||
if (dataFile.exists()) {
|
||||
throw new DuplicateFileException(getName() + " already exists.");
|
|
@ -18,10 +18,13 @@ package ghidra.framework.store.local;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import db.buffers.*;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.db.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -49,8 +52,8 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
* @param create if true the data directory will be created
|
||||
* @throws IOException
|
||||
*/
|
||||
private LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean create)
|
||||
throws IOException {
|
||||
private LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||
boolean create) throws IOException {
|
||||
super(fileSystem, propertyFile, true, create);
|
||||
if (isVersioned) {
|
||||
versionedDbListener = new LocalVersionedDbListener();
|
||||
|
@ -63,7 +66,8 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
* @param fileSystem file system
|
||||
* @param propertyFile database property file
|
||||
*/
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile) throws IOException {
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||
throws IOException {
|
||||
super(fileSystem, propertyFile, true, false);
|
||||
|
||||
if (isVersioned) {
|
||||
|
@ -94,11 +98,16 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
* @throws IOException if error occurs
|
||||
* @throws CancelledException if database creation cancelled by user
|
||||
*/
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, BufferFile srcFile,
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, BufferFile srcFile,
|
||||
String contentType, String fileID, String comment, boolean resetDatabaseId,
|
||||
TaskMonitor monitor, String user) throws IOException, CancelledException {
|
||||
super(fileSystem, propertyFile, true, true);
|
||||
|
||||
if (StringUtils.isBlank(contentType)) {
|
||||
abortCreate();
|
||||
throw new IllegalArgumentException("Missing content-type");
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
long checkoutId = DEFAULT_CHECKOUT_ID;
|
||||
try {
|
||||
|
@ -154,7 +163,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
* @throws IOException if error occurs
|
||||
* @throws CancelledException if database creation cancelled by user
|
||||
*/
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, PropertyFile propertyFile, File packedFile,
|
||||
LocalDatabaseItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, File packedFile,
|
||||
String contentType, TaskMonitor monitor, String user)
|
||||
throws IOException, CancelledException {
|
||||
super(fileSystem, propertyFile, true, true);
|
||||
|
@ -222,7 +231,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
* @throws IOException if error occurs
|
||||
*/
|
||||
static LocalManagedBufferFile create(final LocalFileSystem fileSystem,
|
||||
PropertyFile propertyFile, int bufferSize, String contentType, String fileID,
|
||||
ItemPropertyFile propertyFile, int bufferSize, String contentType, String fileID,
|
||||
String user, String projectPath) throws IOException {
|
||||
|
||||
final LocalDatabaseItem dbItem = new LocalDatabaseItem(fileSystem, propertyFile, true);
|
||||
|
@ -257,6 +266,7 @@ public class LocalDatabaseItem extends LocalFolderItem implements DatabaseItem {
|
|||
db.setSynchronizationObject(dbItem.fileSystem);
|
||||
dbItem.privateDb = (PrivateDatabase) db;
|
||||
}
|
||||
dbItem.log("file created", user);
|
||||
dbItem.fireItemCreated();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,11 +82,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Construct a local filesystem for existing data
|
||||
* @param rootPath
|
||||
* @param create
|
||||
* @param isVersioned
|
||||
* @param readOnly
|
||||
* @param enableAsyncronousDispatching
|
||||
* @param rootPath filesystem root directory (the directory must exist and must not have any
|
||||
* contents if {@code create} is true)
|
||||
* @param create true if creating new filesystem from the empty directory at rootPath
|
||||
* @param isVersioned true if creating a versioned filesystem
|
||||
* @param readOnly true if file system is read-only (ignored if {@code create} is true).
|
||||
* @param enableAsyncronousDispatching true if async event dispatching should be performed
|
||||
* @return local filesystem
|
||||
* @throws FileNotFoundException if specified rootPath does not exist
|
||||
* @throws IOException if error occurs while reading/writing index files
|
||||
|
@ -103,10 +104,6 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
throw new IOException("new filesystem directory is not empty: " + rootPath);
|
||||
}
|
||||
if (create) {
|
||||
// if (isCreateMangledFileSystemEnabled()) {
|
||||
// return new MangledLocalFileSystem(rootPath, isVersioned, readOnly,
|
||||
// enableAsyncronousDispatching);
|
||||
// }
|
||||
return new IndexedV1LocalFileSystem(rootPath, isVersioned, readOnly,
|
||||
enableAsyncronousDispatching, true);
|
||||
}
|
||||
|
@ -154,7 +151,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
/**
|
||||
* Returns true if any file found within dir whose name starts
|
||||
* with '~' character (e.g., ~index.dat, etc)
|
||||
* @param dir
|
||||
* @param dir directory to inspect
|
||||
* @return true if any hidden file found with '~' prefix
|
||||
*/
|
||||
private static boolean hasAnyHiddenFiles(File dir) {
|
||||
|
@ -237,7 +234,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Associate file system with a specific repository logger
|
||||
* @param repositoryLogger
|
||||
* @param repositoryLogger repository logger (may be null)
|
||||
*/
|
||||
public void setAssociatedRepositoryLogger(RepositoryLogger repositoryLogger) {
|
||||
this.repositoryLogger = repositoryLogger;
|
||||
|
@ -317,8 +314,14 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
return pfile.exists();
|
||||
}
|
||||
|
||||
PropertyFile getPropertyFile() throws IOException {
|
||||
return new PropertyFile(dir, storageName, folderPath, itemName);
|
||||
/**
|
||||
* Get property file associated with this item storage
|
||||
* @return property file
|
||||
* @throws InvalidObjectException if a file parse error occurs
|
||||
* @throws IOException if an IO error occurs reading an existing file
|
||||
*/
|
||||
ItemPropertyFile getPropertyFile() throws IOException {
|
||||
return new ItemPropertyFile(dir, storageName, folderPath, itemName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -336,19 +339,19 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Find an existing storage location
|
||||
* @param folderPath
|
||||
* @param itemName
|
||||
* @param folderPath folder path of item
|
||||
* @param itemName item name
|
||||
* @return storage location. A non-null value does not guarantee that the associated
|
||||
* item actually exists.
|
||||
* @throws FileNotFoundException
|
||||
* @throws FileNotFoundException if existing storage allocation not found
|
||||
*/
|
||||
protected abstract ItemStorage findItemStorage(String folderPath, String itemName)
|
||||
throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Allocate a new storage location
|
||||
* @param folderPath
|
||||
* @param itemName
|
||||
* @param folderPath folder path of item
|
||||
* @param itemName item name
|
||||
* @return storage location
|
||||
* @throws DuplicateFileException if item path has previously been allocated
|
||||
* @throws IOException if invalid path/item name specified
|
||||
|
@ -359,9 +362,9 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Deallocate item storage
|
||||
* @param folderPath
|
||||
* @param itemName
|
||||
* @throws IOException
|
||||
* @param folderPath folder path of item
|
||||
* @param itemName item name
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
protected abstract void deallocateItemStorage(String folderPath, String itemName)
|
||||
throws IOException;
|
||||
|
@ -376,15 +379,27 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
@Override
|
||||
public synchronized LocalFolderItem getItem(String folderPath, String name) throws IOException {
|
||||
ItemStorage itemStorage = null;
|
||||
try {
|
||||
ItemStorage itemStorage = findItemStorage(folderPath, name);
|
||||
itemStorage = findItemStorage(folderPath, name);
|
||||
if (itemStorage == null) {
|
||||
return null;
|
||||
}
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
if (propertyFile.exists()) {
|
||||
return LocalFolderItem.getFolderItem(this, propertyFile);
|
||||
}
|
||||
|
||||
// force cleanup of bad storage allocation
|
||||
Msg.warn(this, "Attempting item cleanup due to missing property file: " +
|
||||
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||
itemDeleted(folderPath, name);
|
||||
}
|
||||
catch (InvalidObjectException e) {
|
||||
// Use unknown placeholder item on failure
|
||||
InvalidPropertyFile invalidFile = new InvalidPropertyFile(itemStorage.dir,
|
||||
itemStorage.storageName, itemStorage.folderPath, itemStorage.itemName);
|
||||
return new LocalUnknownFolderItem(this, invalidFile);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
|
@ -394,11 +409,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Notification that FileID has been changed within propertyFile
|
||||
* @param propertyFile
|
||||
* @param oldFileId
|
||||
* @throws IOException
|
||||
* @param propertyFile item property file
|
||||
* @param oldFileId old FileId
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
protected void fileIdChanged(PropertyFile propertyFile, String oldFileId) throws IOException {
|
||||
protected void fileIdChanged(ItemPropertyFile propertyFile, String oldFileId)
|
||||
throws IOException {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
|
@ -418,6 +434,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
return folderItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedItemType(FolderItem folderItem) {
|
||||
return (folderItem instanceof DatabaseItem) || (folderItem instanceof TextDataItem) ||
|
||||
(folderItem instanceof DataFileItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized LocalDatabaseItem createDatabase(String parentPath, String name,
|
||||
String fileID, BufferFile bufferFile, String comment, String contentType,
|
||||
|
@ -434,7 +456,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||
LocalDatabaseItem item = null;
|
||||
try {
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID,
|
||||
comment, resetDatabaseId, monitor, user);
|
||||
}
|
||||
|
@ -462,7 +484,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
ItemStorage itemStorage = allocateItemStorage(parentPath, hiddenName);
|
||||
LocalDatabaseItem item = null;
|
||||
try {
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
item = new LocalDatabaseItem(this, propertyFile, bufferFile, contentType, fileID, null,
|
||||
resetDatabaseId, monitor, null);
|
||||
}
|
||||
|
@ -489,7 +511,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||
LocalManagedBufferFile bufferFile = null;
|
||||
try {
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
bufferFile = LocalDatabaseItem.create(this, propertyFile, bufferSize, contentType,
|
||||
fileID, user, projectPath);
|
||||
}
|
||||
|
@ -502,7 +524,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized LocalDataFile createDataFile(String parentPath, String name,
|
||||
public synchronized LocalDataFileItem createDataFile(String parentPath, String name,
|
||||
InputStream istream, String comment, String contentType, TaskMonitor monitor)
|
||||
throws InvalidNameException, IOException, CancelledException {
|
||||
|
||||
|
@ -514,11 +536,12 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
testValidName(name, false);
|
||||
|
||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||
LocalDataFile dataFile = null;
|
||||
LocalDataFileItem dataFile = null;
|
||||
try {
|
||||
//TODO handle comment
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
dataFile = new LocalDataFile(this, propertyFile, istream, contentType, monitor);
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
dataFile = new LocalDataFileItem(this, propertyFile, istream, contentType, monitor);
|
||||
dataFile.log("file created", getUserName());
|
||||
}
|
||||
finally {
|
||||
if (dataFile == null) {
|
||||
|
@ -531,6 +554,38 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
return dataFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized LocalTextDataItem createTextDataItem(String parentPath, String name,
|
||||
String fileID, String contentType, String textData, String ignoredComment)
|
||||
throws InvalidNameException, IOException {
|
||||
|
||||
// comment is ignored
|
||||
|
||||
if (readOnly) {
|
||||
throw new ReadOnlyException();
|
||||
}
|
||||
|
||||
testValidName(parentPath, true);
|
||||
testValidName(name, false);
|
||||
|
||||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||
LocalTextDataItem linkFile = null;
|
||||
try {
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
linkFile = new LocalTextDataItem(this, propertyFile, fileID, contentType, textData);
|
||||
linkFile.log("file created", getUserName());
|
||||
}
|
||||
finally {
|
||||
if (linkFile == null) {
|
||||
deallocateItemStorage(parentPath, name);
|
||||
}
|
||||
}
|
||||
|
||||
eventManager.itemCreated(parentPath, name);
|
||||
|
||||
return linkFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDatabaseItem createFile(String parentPath, String name, File packedFile,
|
||||
TaskMonitor monitor, String user)
|
||||
|
@ -561,7 +616,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
ItemStorage itemStorage = allocateItemStorage(parentPath, name);
|
||||
LocalDatabaseItem item = null;
|
||||
try {
|
||||
PropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
ItemPropertyFile propertyFile = itemStorage.getPropertyFile();
|
||||
item =
|
||||
new LocalDatabaseItem(this, propertyFile, packedFile, contentType, monitor, user);
|
||||
}
|
||||
|
@ -661,6 +716,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Returns file system listener.
|
||||
* @return file system listener or null
|
||||
*/
|
||||
FileSystemListener getListener() {
|
||||
return eventManager;
|
||||
|
@ -716,6 +772,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param c character to check
|
||||
* @return true if c is a valid character within the FileSystem.
|
||||
*/
|
||||
public static boolean isValidNameCharacter(char c) {
|
||||
|
@ -756,9 +813,9 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
/**
|
||||
* Notify the filesystem that the property file and associated data files for
|
||||
* an item have been removed from the filesystem.
|
||||
* @param folderPath
|
||||
* @param itemName
|
||||
* @throws IOException
|
||||
* @param folderPath folder path of item
|
||||
* @param itemName item name
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
protected synchronized void itemDeleted(String folderPath, String itemName) throws IOException {
|
||||
// do nothing
|
||||
|
@ -768,6 +825,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
* Returns the full path for a specific folder or item
|
||||
* @param parentPath full parent path
|
||||
* @param name child folder or item name
|
||||
* @return pathname
|
||||
*/
|
||||
protected final static String getPath(String parentPath, String name) {
|
||||
if (parentPath.length() == 1) {
|
||||
|
@ -848,7 +906,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Escape hidden prefix chars in name
|
||||
* @param name
|
||||
* @param name name to be escaped
|
||||
* @return escaped name
|
||||
*/
|
||||
public static final String escapeHiddenDirPrefixChars(String name) {
|
||||
|
@ -867,7 +925,7 @@ public abstract class LocalFileSystem implements FileSystem {
|
|||
|
||||
/**
|
||||
* Unescape a non-hidden directory name
|
||||
* @param name
|
||||
* @param name name to be unescaped
|
||||
* @return unescaped name or null if name is a hidden name
|
||||
*/
|
||||
public static final String unescapeHiddenDirPrefixChars(String name) {
|
||||
|
|
|
@ -21,7 +21,8 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utilities.util.FileUtilities;
|
||||
|
@ -49,7 +50,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
|
||||
static final String DATA_DIR_EXTENSION = ".db";
|
||||
|
||||
final PropertyFile propertyFile;
|
||||
final ItemPropertyFile propertyFile;
|
||||
final CheckoutManager checkoutMgr;
|
||||
final HistoryManager historyMgr;
|
||||
final LocalFileSystem fileSystem;
|
||||
|
@ -69,7 +70,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
* @param fileSystem file system
|
||||
* @param propertyFile property file
|
||||
*/
|
||||
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
||||
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
|
||||
this.fileSystem = fileSystem;
|
||||
this.propertyFile = propertyFile;
|
||||
this.isVersioned = fileSystem.isVersioned();
|
||||
|
@ -90,7 +91,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
* @param create if true the data directory will be created
|
||||
* @throws IOException
|
||||
*/
|
||||
LocalFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile, boolean useDataDir,
|
||||
LocalFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile, boolean useDataDir,
|
||||
boolean create) throws IOException {
|
||||
this.fileSystem = fileSystem;
|
||||
this.propertyFile = propertyFile;
|
||||
|
@ -121,7 +122,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
throw new FileNotFoundException(getName() + " not found");
|
||||
}
|
||||
|
||||
if (isVersioned) {
|
||||
if (isVersioned && useDataDir) {
|
||||
checkoutMgr = new CheckoutManager(this, create);
|
||||
historyMgr = new HistoryManager(this, create);
|
||||
}
|
||||
|
@ -161,7 +162,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
final File getDataDir() {
|
||||
synchronized (fileSystem) {
|
||||
// Use hidden DB directory
|
||||
return new File(propertyFile.getFolder(),
|
||||
return new File(propertyFile.getParentStorageDirectory(),
|
||||
LocalFileSystem.HIDDEN_DIR_PREFIX +
|
||||
LocalFileSystem.escapeHiddenDirPrefixChars(propertyFile.getStorageName()) +
|
||||
DATA_DIR_EXTENSION);
|
||||
|
@ -234,6 +235,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
*/
|
||||
void beginCheckin(long checkoutId) throws FileInUseException {
|
||||
synchronized (fileSystem) {
|
||||
if (checkoutMgr == null) {
|
||||
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||
}
|
||||
if (checkinId != DEFAULT_CHECKOUT_ID) {
|
||||
ItemCheckoutStatus status;
|
||||
try {
|
||||
|
@ -426,7 +430,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
synchronized (fileSystem) {
|
||||
checkInUse();
|
||||
|
||||
File oldFolder = propertyFile.getFolder();
|
||||
File oldFolder = propertyFile.getParentStorageDirectory();
|
||||
String oldStorageName = propertyFile.getStorageName();
|
||||
String oldPath = propertyFile.getParentPath();
|
||||
File oldDbDir = getDataDir();
|
||||
|
@ -491,41 +495,6 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
return propertyFile.getName();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Change the name of this item's property file and hidden data directory
|
||||
// * based upon the new item name.
|
||||
// * If in-use files prevent renaming a FileInUseException will be thrown.
|
||||
// * @param name new name for this item
|
||||
// * @throws InvalidNameException invalid name was specified
|
||||
// * @throws IOException an error occurred
|
||||
// */
|
||||
// void doSetName(String name) throws InvalidNameException, IOException {
|
||||
// synchronized (fileSystem) {
|
||||
// File oldDbDir = getDataDir();
|
||||
// String oldName = getName();
|
||||
//
|
||||
// boolean success = false;
|
||||
// try {
|
||||
// propertyFile.setName(name);
|
||||
// File newDbDir = getDataDir();
|
||||
// if (useDataDir) {
|
||||
// if (newDbDir.exists()) {
|
||||
// throw new DuplicateFileException(getName() + " already exists");
|
||||
// }
|
||||
// else if (!oldDbDir.renameTo(newDbDir)) {
|
||||
// throw new FileInUseException(oldName + " is in use");
|
||||
// }
|
||||
// }
|
||||
// success = true;
|
||||
// }
|
||||
// finally {
|
||||
// if (!success && !propertyFile.getName().equals(oldName)) {
|
||||
// propertyFile.setName(oldName);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @see ghidra.framework.store.FolderItem#getParentPath()
|
||||
*/
|
||||
|
@ -590,6 +559,10 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
throw new UnsupportedOperationException(
|
||||
"Non-versioned item does not support getVersions");
|
||||
}
|
||||
if (historyMgr == null) {
|
||||
throw new UnsupportedOperationException(
|
||||
"getVersions not supported without history manager");
|
||||
}
|
||||
return historyMgr.getVersions();
|
||||
}
|
||||
}
|
||||
|
@ -652,12 +625,16 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
@Override
|
||||
public ItemCheckoutStatus checkout(CheckoutType checkoutType, String user, String projectPath)
|
||||
throws IOException {
|
||||
if (checkoutMgr == null) {
|
||||
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||
}
|
||||
if (!isVersioned) {
|
||||
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
||||
}
|
||||
if (fileSystem.isReadOnly()) {
|
||||
throw new ReadOnlyException();
|
||||
}
|
||||
|
||||
synchronized (fileSystem) {
|
||||
|
||||
ItemCheckoutStatus coStatus =
|
||||
|
@ -672,6 +649,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
|
||||
@Override
|
||||
public void terminateCheckout(long checkoutId, boolean notify) throws IOException {
|
||||
if (checkoutMgr == null) {
|
||||
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||
}
|
||||
if (!isVersioned) {
|
||||
throw new UnsupportedOperationException("Non-versioned item does not support checkout");
|
||||
}
|
||||
|
@ -700,6 +680,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
throw new UnsupportedOperationException(
|
||||
"Non-versioned item does not support checkout");
|
||||
}
|
||||
if (checkoutMgr == null) {
|
||||
return null;
|
||||
}
|
||||
return checkoutMgr.getCheckout(checkoutId);
|
||||
}
|
||||
}
|
||||
|
@ -711,6 +694,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
throw new UnsupportedOperationException(
|
||||
"Non-versioned item does not support checkout");
|
||||
}
|
||||
if (checkoutMgr == null) {
|
||||
return new ItemCheckoutStatus[0];
|
||||
}
|
||||
return checkoutMgr.getAllCheckouts();
|
||||
}
|
||||
}
|
||||
|
@ -802,33 +788,39 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
* @param propertyFile property file which identifies the folder item.
|
||||
* @return folder item
|
||||
*/
|
||||
static LocalFolderItem getFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
||||
static LocalFolderItem getFolderItem(LocalFileSystem fileSystem,
|
||||
ItemPropertyFile propertyFile) {
|
||||
int fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
||||
try {
|
||||
if (fileType == DATAFILE_FILE_TYPE) {
|
||||
return new LocalDataFile(fileSystem, propertyFile);
|
||||
return new LocalDataFileItem(fileSystem, propertyFile);
|
||||
}
|
||||
else if (fileType == DATABASE_FILE_TYPE) {
|
||||
return new LocalDatabaseItem(fileSystem, propertyFile);
|
||||
}
|
||||
else if (fileType == LINK_FILE_TYPE) {
|
||||
return new LocalTextDataItem(fileSystem, propertyFile);
|
||||
}
|
||||
else if (fileType == UNKNOWN_FILE_TYPE) {
|
||||
log.error("Folder item has unspecified file type: " +
|
||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
|
||||
log.error("Folder item has unspecified file type: " + new File(
|
||||
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||
}
|
||||
else {
|
||||
log.error("Folder item has unsupported file type (" + fileType + "): " +
|
||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()));
|
||||
log.error("Folder item has unsupported file type (" + fileType + "): " + new File(
|
||||
propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()));
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
log.error("Folder item may be corrupt due to missing file: " +
|
||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()), e);
|
||||
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()),
|
||||
e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.error("Folder item may be corrupt: " +
|
||||
new File(propertyFile.getFolder(), propertyFile.getStorageName()), e);
|
||||
new File(propertyFile.getParentStorageDirectory(), propertyFile.getStorageName()),
|
||||
e);
|
||||
}
|
||||
return new UnknownFolderItem(fileSystem, propertyFile);
|
||||
return new LocalUnknownFolderItem(fileSystem, propertyFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -836,7 +828,7 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
synchronized (fileSystem) {
|
||||
if (isVersioned) {
|
||||
try {
|
||||
return checkoutMgr.isCheckedOut();
|
||||
return checkoutMgr != null && checkoutMgr.isCheckedOut();
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(getName() + " versioning error", e);
|
||||
|
@ -865,6 +857,11 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return propertyFile.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this non-versioned item with the latest version of the specified versioned item.
|
||||
* @param versionedFolderItem versioned item which corresponds to this
|
||||
|
@ -892,6 +889,9 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
@Override
|
||||
public void updateCheckoutVersion(long checkoutId, int checkoutVersion, String user)
|
||||
throws IOException {
|
||||
if (checkoutMgr == null) {
|
||||
throw new UnsupportedOperationException("item does not support checkin/checkout");
|
||||
}
|
||||
if (!isVersioned) {
|
||||
throw new UnsupportedOperationException(
|
||||
"updateCheckoutVersion is not applicable to non-versioned item");
|
||||
|
@ -907,4 +907,5 @@ public abstract class LocalFolderItem implements FolderItem {
|
|||
checkoutMgr.updateCheckout(checkoutId, checkoutVersion);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.framework.store.local;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* <code>LocalTextDataItem</code> provides a {@link LocalFolderItem} implementation
|
||||
* which stores text data within the associated propertyFile and without any other data storage.
|
||||
*/
|
||||
public class LocalTextDataItem extends LocalFolderItem implements TextDataItem {
|
||||
|
||||
private static final String TEXT_PROPERTY = "TEXT";
|
||||
private static final String VERSION_CREATE_USER = "CREATE_USER";
|
||||
private static final String VERSION_CREATE_TIME = "CREATE_TIME";
|
||||
private static final String VERSION_CREATE_COMMENT = "CREATE_COMMENT";
|
||||
|
||||
/**
|
||||
* Constructor for an existing local link file item which corresponds to the specified
|
||||
* property file.
|
||||
* @param fileSystem file system
|
||||
* @param propertyFile database property file
|
||||
* @throws IOException if an IO Error occurs
|
||||
*/
|
||||
public LocalTextDataItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile)
|
||||
throws IOException {
|
||||
super(fileSystem, propertyFile, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new local text data file item.
|
||||
* @param fileSystem file system
|
||||
* @param propertyFile serialized data property file
|
||||
* @param fileID file ID to be associated with new file or null
|
||||
* @param contentType user content type
|
||||
* @param textData text to be stored within associated property file
|
||||
* @throws IOException if an IO Error occurs
|
||||
*/
|
||||
public LocalTextDataItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile,
|
||||
String fileID, String contentType, String textData) throws IOException {
|
||||
super(fileSystem, propertyFile, false, true);
|
||||
|
||||
if (StringUtils.isBlank(contentType)) {
|
||||
abortCreate();
|
||||
throw new IllegalArgumentException("Missing content-type");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(textData)) {
|
||||
abortCreate();
|
||||
throw new IllegalArgumentException("Missing text data");
|
||||
}
|
||||
|
||||
propertyFile.putInt(FILE_TYPE, LINK_FILE_TYPE);
|
||||
propertyFile.putBoolean(READ_ONLY, false);
|
||||
propertyFile.putString(CONTENT_TYPE, contentType);
|
||||
if (fileID != null) {
|
||||
propertyFile.setFileID(fileID);
|
||||
}
|
||||
|
||||
propertyFile.putString(TEXT_PROPERTY, textData);
|
||||
|
||||
propertyFile.writeState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text data that was stored with this item
|
||||
* @return text data
|
||||
*/
|
||||
public String getTextData() {
|
||||
return propertyFile.getString(TEXT_PROPERTY, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
throw new IOException("Versioning updates not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
|
||||
throw new IOException("Versioning updates not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
void deleteMinimumVersion(String user) throws IOException {
|
||||
throw new UnsupportedOperationException("Versioning updates not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
void deleteCurrentVersion(String user) throws IOException {
|
||||
throw new UnsupportedOperationException("Versioning updates not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
|
||||
throw new IOException("Output not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinimumVersion() {
|
||||
return getCurrentVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentVersion() {
|
||||
return 1; // only a single version of the file may exist
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRecover() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the version info associated with this versioned file. Only a single version is
|
||||
* supported.
|
||||
* @param version version information (only user, create time and comment is retained)
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public void setVersionInfo(Version version) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (!isVersioned()) {
|
||||
throw new UnsupportedOperationException("Versioning not supported");
|
||||
}
|
||||
propertyFile.putString(VERSION_CREATE_USER, version.getUser());
|
||||
propertyFile.putLong(VERSION_CREATE_TIME, version.getCreateTime());
|
||||
propertyFile.putString(VERSION_CREATE_COMMENT, version.getComment());
|
||||
propertyFile.writeState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Version[] getVersions() throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (!isVersioned) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Non-versioned item does not support getVersions");
|
||||
}
|
||||
String createUser = propertyFile.getString(VERSION_CREATE_USER, "");
|
||||
long createTime = propertyFile.getLong(VERSION_CREATE_TIME, 0);
|
||||
String comment = propertyFile.getString(VERSION_CREATE_COMMENT, null);
|
||||
return new Version[] { new Version(1, createTime, createUser, comment) };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,16 +19,13 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.util.PropertyFile;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* <code>UnknownFolderItem</code> acts as a LocalFolderItem place-holder for
|
||||
* items of an unknown type.
|
||||
*/
|
||||
public class UnknownFolderItem extends LocalFolderItem {
|
||||
|
||||
public static final String UNKNOWN_CONTENT_TYPE = "Unknown-File";
|
||||
public class LocalUnknownFolderItem extends LocalFolderItem implements UnknownFolderItem {
|
||||
|
||||
private final int fileType;
|
||||
|
||||
|
@ -37,7 +34,7 @@ public class UnknownFolderItem extends LocalFolderItem {
|
|||
* @param fileSystem local file system
|
||||
* @param propertyFile property file associated with this item
|
||||
*/
|
||||
UnknownFolderItem(LocalFileSystem fileSystem, PropertyFile propertyFile) {
|
||||
LocalUnknownFolderItem(LocalFileSystem fileSystem, ItemPropertyFile propertyFile) {
|
||||
super(fileSystem, propertyFile);
|
||||
fileType = propertyFile.getInt(FILE_TYPE, UNKNOWN_FILE_TYPE);
|
||||
}
|
||||
|
@ -55,134 +52,82 @@ public class UnknownFolderItem extends LocalFolderItem {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, boolean, ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void updateCheckout(FolderItem versionedFolderItem, boolean updateItem,
|
||||
TaskMonitor monitor) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#updateCheckout(ghidra.framework.store.FolderItem, int)
|
||||
*/
|
||||
@Override
|
||||
public void updateCheckout(FolderItem item, int checkoutVersion) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#checkout(java.lang.String)
|
||||
*/
|
||||
public synchronized ItemCheckoutStatus checkout(String user) throws IOException {
|
||||
throw new IOException(propertyFile.getName() +
|
||||
" may not be checked-out, item may be corrupt");
|
||||
throw new IOException(
|
||||
propertyFile.getName() + " may not be checked-out, item may be corrupt");
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#terminateCheckout(long)
|
||||
*/
|
||||
public synchronized void terminateCheckout(long checkoutId) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#clearCheckout()
|
||||
*/
|
||||
@Override
|
||||
public void clearCheckout() throws IOException {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#setCheckout(long, int, int)
|
||||
*/
|
||||
public void setCheckout(long checkoutId, int checkoutVersion, int localVersion) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#getCheckout(long)
|
||||
*/
|
||||
@Override
|
||||
public synchronized ItemCheckoutStatus getCheckout(long checkoutId) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#getCheckouts()
|
||||
*/
|
||||
@Override
|
||||
public synchronized ItemCheckoutStatus[] getCheckouts() throws IOException {
|
||||
return new ItemCheckoutStatus[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#getVersions()
|
||||
*/
|
||||
@Override
|
||||
public synchronized Version[] getVersions() throws IOException {
|
||||
throw new IOException("History data is unavailable for " + propertyFile.getName());
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#getContentType()
|
||||
*/
|
||||
@Override
|
||||
public String getContentType() {
|
||||
// NOTE: We could get the content type from the property file but we don't want any
|
||||
// attempt to use it
|
||||
return UNKNOWN_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.local.LocalFolderItem#deleteMinimumVersion(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
void deleteMinimumVersion(String user) throws IOException {
|
||||
|
||||
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.local.LocalFolderItem#deleteCurrentVersion(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
void deleteCurrentVersion(String user) throws IOException {
|
||||
|
||||
throw new UnsupportedOperationException("Versioning not supported for UnknownFolderItems");
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#output(java.io.File, int, ghidra.util.task.TaskMonitor)
|
||||
*/
|
||||
@Override
|
||||
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException {
|
||||
|
||||
throw new UnsupportedOperationException("Output not supported for UnknownFolderItems");
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.local.LocalFolderItem#getMinimumVersion()
|
||||
*/
|
||||
@Override
|
||||
int getMinimumVersion() throws IOException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#getCurrentVersion()
|
||||
*/
|
||||
@Override
|
||||
public int getCurrentVersion() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see ghidra.framework.store.FolderItem#canRecover()
|
||||
*/
|
||||
@Override
|
||||
public boolean canRecover() {
|
||||
return false;
|