mirror of
https://github.com/NationalSecurityAgency/ghidra
synced 2024-08-27 21:10:26 +00:00
GP-4433 Revised opening/viewing project folder/file links
This commit is contained in:
parent
146c177343
commit
38a01c0434
|
@ -24,6 +24,7 @@ import ghidra.framework.main.AppInfo;
|
|||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -45,8 +46,7 @@ public class FolderLinkContentHandler extends LinkHandler<NullFolderDomainObject
|
|||
if (!(obj instanceof URLLinkObject)) {
|
||||
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
|
||||
}
|
||||
return createFile((URLLinkObject) obj, FOLDER_LINK_CONTENT_TYPE, fs, path, name,
|
||||
monitor);
|
||||
return createFile((URLLinkObject) obj, FOLDER_LINK_CONTENT_TYPE, fs, path, name, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,6 +91,11 @@ public class FolderLinkContentHandler extends LinkHandler<NullFolderDomainObject
|
|||
URL url = getURL(folderLinkFile);
|
||||
|
||||
Project activeProject = AppInfo.getActiveProject();
|
||||
if (activeProject == null) {
|
||||
Msg.error(FolderLinkContentHandler.class,
|
||||
"Use of Linked Folders requires active project.");
|
||||
return null;
|
||||
}
|
||||
GhidraFolder parent = ((GhidraFile) folderLinkFile).getParent();
|
||||
return new LinkedGhidraFolder(activeProject, parent, folderLinkFile.getName(), url);
|
||||
}
|
||||
|
|
|
@ -1067,10 +1067,14 @@ public class GhidraFileData {
|
|||
if (folderItem.isCheckedOut() || versionedFolderItem != null) {
|
||||
throw new IOException("File already versioned");
|
||||
}
|
||||
if (isLinkFile() && !GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) {
|
||||
throw new IOException("Local project link-file may not be versioned");
|
||||
ContentHandler<?> contentHandler = getContentHandler();
|
||||
if (contentHandler instanceof LinkHandler linkHandler) {
|
||||
// must check local vs remote URL
|
||||
if (!GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) {
|
||||
throw new IOException("Local project link-file may not be versioned");
|
||||
}
|
||||
}
|
||||
if (getContentHandler().isPrivateContentType()) {
|
||||
else if (contentHandler.isPrivateContentType()) {
|
||||
throw new IOException("Content may not be versioned: " + getContentType());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,8 +165,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
|
|||
|
||||
@Override
|
||||
public final boolean isPrivateContentType() {
|
||||
// NOTE: URL must be checked - only repository-based links may be versioned
|
||||
return true;
|
||||
throw new UnsupportedOperationException("Link file requires checking server vs local URL");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -34,8 +34,7 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder {
|
|||
|
||||
public static Icon FOLDER_LINK_CLOSED_ICON =
|
||||
new GIcon("icon.content.handler.linked.folder.closed");
|
||||
public static Icon FOLDER_LINK_OPEN_ICON =
|
||||
new GIcon("icon.content.handler.linked.folder.open");
|
||||
public static Icon FOLDER_LINK_OPEN_ICON = new GIcon("icon.content.handler.linked.folder.open");
|
||||
|
||||
private final Project activeProject;
|
||||
private final DomainFolder localParent;
|
||||
|
@ -73,8 +72,8 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the Ghidra URL associated with this linked folder's project or repository
|
||||
* @return Ghidra URL associated with this linked folder's project or repository
|
||||
* Get the Ghidra URL of the project/repository folder referenced by this object
|
||||
* @return Ghidra URL of the project/repository folder referenced by this object
|
||||
*/
|
||||
public URL getProjectURL() {
|
||||
if (projectUrl == null) {
|
||||
|
@ -83,6 +82,7 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder {
|
|||
return projectUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
LinkedGhidraFolder getLinkedRootFolder() {
|
||||
return this;
|
||||
}
|
||||
|
@ -108,12 +108,12 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder {
|
|||
|
||||
@Override
|
||||
public ProjectLocator getProjectLocator() {
|
||||
return activeProject.getProjectLocator();
|
||||
return localParent.getProjectLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectData getProjectData() {
|
||||
return activeProject.getProjectData();
|
||||
return localParent.getProjectData();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -28,6 +28,10 @@ import ghidra.util.*;
|
|||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@code LinkedGhidraSubFolder} corresponds to a {@link DomainFolder} contained within a
|
||||
* {@link LinkedGhidraFolder} or another {@code LinkedGhidraSubFolder}.
|
||||
*/
|
||||
class LinkedGhidraSubFolder implements LinkedDomainFolder {
|
||||
|
||||
private final LinkedGhidraFolder linkedRootFolder;
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
*/
|
||||
package ghidra.framework.main;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.framework.data.FolderLinkContentHandler;
|
||||
import ghidra.framework.main.datatree.ProjectDataTreePanel;
|
||||
import ghidra.framework.model.DomainFile;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLQueryTask;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -36,33 +36,87 @@ public class AcceptUrlContentTask extends GhidraURLQueryTask {
|
|||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private boolean isSameLocalProject(ProjectLocator projectLoc1, ProjectLocator projectLoc2) {
|
||||
if (projectLoc1.isTransient() || projectLoc2.isTransient()) {
|
||||
return false;
|
||||
}
|
||||
if (!projectLoc1.getName().equals(projectLoc2.getName())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
File proj1Dir = projectLoc1.getProjectDir().getCanonicalFile();
|
||||
File proj2Dir = projectLoc2.getProjectDir().getCanonicalFile();
|
||||
return proj1Dir.equals(proj2Dir);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processResult(DomainFile domainFile, URL url, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
Swing.runNow(() -> {
|
||||
|
||||
Project activeProject = AppInfo.getActiveProject();
|
||||
if (activeProject == null) {
|
||||
Msg.showError(this, null, "Ghidra Error",
|
||||
"Unable to accept URL without active project open");
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runNow(() -> {
|
||||
if (FolderLinkContentHandler.FOLDER_LINK_CONTENT_TYPE
|
||||
.equals(domainFile.getContentType())) {
|
||||
plugin.showLinkedFolder(domainFile);
|
||||
return;
|
||||
// Simply select folder link-file within project - do not follow - let user do that.
|
||||
if (isSameLocalProject(activeProject.getProjectLocator(),
|
||||
domainFile.getProjectLocator())) {
|
||||
// Select file within active project
|
||||
DomainFile df =
|
||||
activeProject.getProjectData().getFile(domainFile.getPathname());
|
||||
if (df == null) {
|
||||
return; // unexpected race condition
|
||||
}
|
||||
plugin.selectFiles(Set.of(df));
|
||||
}
|
||||
else {
|
||||
// Select file within read-only viewed project
|
||||
plugin.showInViewedProject(url, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
AppInfo.getFrontEndTool().getToolServices().launchDefaultToolWithURL(url);
|
||||
}
|
||||
AppInfo.getFrontEndTool().getToolServices().launchDefaultToolWithURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processResult(DomainFolder domainFolder, URL url, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
ProjectDataPanel projectDataPanel = plugin.getProjectDataPanel();
|
||||
|
||||
Project activeProject = AppInfo.getActiveProject();
|
||||
if (activeProject == null) {
|
||||
Msg.showError(this, null, "Ghidra Error",
|
||||
"Unable to accept URL without active project open");
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runNow(() -> {
|
||||
ProjectDataTreePanel dtp = projectDataPanel.openView(GhidraURL.getProjectURL(url));
|
||||
if (dtp == null) {
|
||||
return;
|
||||
if (isSameLocalProject(activeProject.getProjectLocator(),
|
||||
domainFolder.getProjectLocator())) {
|
||||
// Select folder within active project
|
||||
DomainFolder df =
|
||||
activeProject.getProjectData().getFolder(domainFolder.getPathname());
|
||||
if (df == null) {
|
||||
return; // unexpected race condition
|
||||
}
|
||||
plugin.selectFolder(df);
|
||||
}
|
||||
else {
|
||||
// Select folder within read-only viewed project
|
||||
plugin.showInViewedProject(url, true);
|
||||
}
|
||||
dtp.selectDomainFolder(domainFolder);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import ghidra.framework.options.SaveState;
|
|||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||
|
@ -539,8 +540,22 @@ public class FrontEndPlugin extends Plugin
|
|||
SwingUtilities.invokeLater(() -> {
|
||||
// there was a delete bug; make the set unmodifiable to catch this earlier
|
||||
Set<DomainFile> unmodifiableFiles = Collections.unmodifiableSet(files);
|
||||
if (dataTablePanel.isCapacityExceeded()) {
|
||||
projectDataPanel.showTree();
|
||||
}
|
||||
else {
|
||||
dataTablePanel.setSelectedDomainFiles(unmodifiableFiles);
|
||||
}
|
||||
dataTreePanel.selectDomainFiles(unmodifiableFiles);
|
||||
dataTablePanel.setSelectedDomainFiles(unmodifiableFiles);
|
||||
});
|
||||
}
|
||||
|
||||
void selectFolder(final DomainFolder folder) {
|
||||
// Do this later in case any of the given files are newly created, which means that the
|
||||
// GUIs may have not yet been notified.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
projectDataPanel.showTree();
|
||||
dataTreePanel.selectDomainFolder(folder);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1081,7 +1096,7 @@ public class FrontEndPlugin extends Plugin
|
|||
public void openDomainFile(DomainFile domainFile) {
|
||||
|
||||
if (FolderLinkContentHandler.FOLDER_LINK_CONTENT_TYPE.equals(domainFile.getContentType())) {
|
||||
showLinkedFolder(domainFile);
|
||||
showLinkedFolderInViewedProject(domainFile);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1109,11 +1124,11 @@ public class FrontEndPlugin extends Plugin
|
|||
"opens this type of file");
|
||||
}
|
||||
|
||||
void showLinkedFolder(DomainFile domainFile) {
|
||||
private void showLinkedFolderInViewedProject(DomainFile domainFile) {
|
||||
|
||||
try {
|
||||
LinkedGhidraFolder linkedFolder =
|
||||
FolderLinkContentHandler.getReadOnlyLinkedFolder(domainFile);
|
||||
|
||||
if (linkedFolder == null) {
|
||||
return; // unsupported use
|
||||
}
|
||||
|
@ -1123,7 +1138,12 @@ public class FrontEndPlugin extends Plugin
|
|||
return;
|
||||
}
|
||||
|
||||
DomainFolder domainFolder = linkedFolder.getLinkedFolder();
|
||||
// Do not hang onto domainFile, linkedFolder or their underlying project data
|
||||
|
||||
ProjectData viewedProjectData = dtp.getProjectData();
|
||||
DomainFolder domainFolder =
|
||||
viewedProjectData.getFolder(linkedFolder.getLinkedPathname());
|
||||
|
||||
if (domainFolder != null) {
|
||||
// delayed to ensure tree is displayed
|
||||
Swing.runLater(() -> dtp.selectDomainFolder(domainFolder));
|
||||
|
@ -1133,6 +1153,53 @@ public class FrontEndPlugin extends Plugin
|
|||
Msg.showError(this, projectDataPanel, "Linked-folder failure: " + domainFile.getName(),
|
||||
e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void showInViewedProject(URL ghidraURL, boolean isFolder) {
|
||||
|
||||
ProjectDataTreePanel dtp = projectDataPanel.openView(GhidraURL.getProjectURL(ghidraURL));
|
||||
if (dtp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runLater(() -> {
|
||||
// delayed to ensure tree is displayed
|
||||
|
||||
ProjectData viewedProjectData = dtp.getProjectData();
|
||||
|
||||
String path = GhidraURL.getProjectPathname(ghidraURL);
|
||||
|
||||
if (isFolder) {
|
||||
DomainFolder viewedProjectFolder = getViewProjectFolder(viewedProjectData, path);
|
||||
if (viewedProjectFolder != null) {
|
||||
dtp.selectDomainFolder(viewedProjectFolder);
|
||||
}
|
||||
}
|
||||
else {
|
||||
DomainFile viewedProjectFile = getViewProjectFile(viewedProjectData, path);
|
||||
if (viewedProjectFile != null) {
|
||||
dtp.selectDomainFile(viewedProjectFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DomainFile getViewProjectFile(ProjectData viewedProjectData, String path) {
|
||||
if (path == null || path.endsWith(DomainFolder.SEPARATOR)) {
|
||||
return null;
|
||||
}
|
||||
return viewedProjectData.getFile(path);
|
||||
}
|
||||
|
||||
private DomainFolder getViewProjectFolder(ProjectData viewedProjectData, String path) {
|
||||
if (path == null || path.equals(DomainFolder.SEPARATOR)) {
|
||||
return viewedProjectData.getRootFolder();
|
||||
}
|
||||
if (path.endsWith(DomainFolder.SEPARATOR)) {
|
||||
path = path.substring(0, path.length() - 1); // remove trailing separator
|
||||
}
|
||||
return viewedProjectData.getFolder(path);
|
||||
}
|
||||
|
||||
private class MyToolChestChangeListener implements ToolChestChangeListener {
|
||||
|
|
|
@ -406,6 +406,10 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener {
|
|||
}
|
||||
}
|
||||
|
||||
void showTree() {
|
||||
projectTab.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private void showTable() {
|
||||
projectTab.setSelectedIndex(1);
|
||||
}
|
||||
|
|
|
@ -103,6 +103,14 @@ public class ProjectDataTablePanel extends JPanel {
|
|||
new ProjectDataTableDnDHandler(gTable, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if table capacity has been exceeded and files are not shown
|
||||
* @return true if files are not shown in project data table, else false
|
||||
*/
|
||||
public boolean isCapacityExceeded() {
|
||||
return capacityExceeded;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
table.dispose(); // this will dispose the gTable as well
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue