GP-3678 Added DomainFile/DomainFolder getLocalProjectURL and revised getSharedProjectURL

This commit is contained in:
ghidra1 2023-08-03 10:25:47 -04:00
parent d33dc66594
commit 0ba065fd67
13 changed files with 178 additions and 76 deletions

View File

@ -540,7 +540,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
this.program = p;
this.domainFile = domainFile;
if (domainFile instanceof LinkedDomainFile linkedDomainFile) {
this.ghidraURL = linkedDomainFile.getSharedProjectURL();
this.ghidraURL = linkedDomainFile.getSharedProjectURL(null);
}
else {
this.ghidraURL = null;

View File

@ -27,6 +27,7 @@ import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@ -86,6 +87,15 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
deleteAll(sharedProjectDir);
}
@Test
public void testLocalURL() throws IOException {
createDB(privateFS, "/a", "file1");
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"),
pfm.getFile("/a/file1").getLocalProjectURL("xyz"));
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null),
pfm.getFile("/a/file1").getLocalProjectURL(null));
}
@Test
public void testFileID() throws IOException {
createDB(privateFS, "/a", "file1");

View File

@ -23,6 +23,8 @@ import java.util.*;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
@ -116,16 +118,13 @@ public class DomainFileProxy implements DomainFile {
return parentPath + DomainFolder.SEPARATOR + getName();
}
private URL getSharedFileURL(URL sharedProjectURL) {
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
try {
// Direct URL construction done so that ghidra protocol
// extension may be supported
String urlStr = sharedProjectURL.toExternalForm();
if (urlStr.endsWith("/")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
String spec = getPathname().substring(1); // remove leading '/'
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
}
urlStr += getPathname();
return new URL(urlStr);
return new URL(sharedProjectURL, spec);
}
catch (MalformedURLException e) {
// ignore
@ -133,7 +132,7 @@ public class DomainFileProxy implements DomainFile {
return null;
}
private URL getSharedFileURL(Properties properties) {
private URL getSharedFileURL(Properties properties, String ref) {
if (properties == null) {
return null;
}
@ -166,9 +165,8 @@ public class DomainFileProxy implements DomainFile {
return null;
}
ServerInfo serverInfo = repository.getServerInfo();
return GhidraURL.makeURL(serverInfo.getServerName(),
serverInfo.getPortNumber(), repository.getName(),
item.getPathName());
return GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName(), item.getPathName(), ref);
}
catch (IOException e) {
// ignore
@ -182,15 +180,27 @@ public class DomainFileProxy implements DomainFile {
}
@Override
public URL getSharedProjectURL() {
public URL getSharedProjectURL(String ref) {
if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) {
URL projectURL = projectLocation.getURL();
if (GhidraURL.isServerRepositoryURL(projectURL)) {
return getSharedFileURL(projectURL);
return getSharedFileURL(projectURL, ref);
}
Properties properties =
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
return getSharedFileURL(properties);
return getSharedFileURL(properties, ref);
}
return null;
}
@Override
public URL getLocalProjectURL(String ref) {
if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) {
URL projectURL = projectLocation.getURL();
if (GhidraURL.isServerRepositoryURL(projectURL)) {
return null;
}
return GhidraURL.makeURL(projectLocation, getPathname(), ref);
}
return null;
}

View File

@ -124,9 +124,20 @@ public class GhidraFile implements DomainFile {
}
@Override
public URL getSharedProjectURL() {
public URL getSharedProjectURL(String ref) {
try {
return getFileData().getSharedProjectURL();
return getFileData().getSharedProjectURL(ref);
}
catch (IOException e) {
// ignore
}
return null;
}
@Override
public URL getLocalProjectURL(String ref) {
try {
return getFileData().getLocalProjectURL(ref);
}
catch (IOException e) {
// ignore
@ -456,10 +467,9 @@ public class GhidraFile implements DomainFile {
}
@Override
public boolean checkout(boolean exclusive, TaskMonitor monitor) throws IOException,
CancelledException {
return getFileData().checkout(exclusive,
monitor != null ? monitor : TaskMonitor.DUMMY);
public boolean checkout(boolean exclusive, TaskMonitor monitor)
throws IOException, CancelledException {
return getFileData().checkout(exclusive, monitor != null ? monitor : TaskMonitor.DUMMY);
}
@Override
@ -470,10 +480,9 @@ public class GhidraFile implements DomainFile {
}
@Override
public void merge(boolean okToUpgrade, TaskMonitor monitor) throws IOException,
VersionException, CancelledException {
getFileData().merge(okToUpgrade,
monitor != null ? monitor : TaskMonitor.DUMMY);
public void merge(boolean okToUpgrade, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
getFileData().merge(okToUpgrade, monitor != null ? monitor : TaskMonitor.DUMMY);
}
@Override
@ -521,8 +530,8 @@ public class GhidraFile implements DomainFile {
}
@Override
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor) throws IOException,
CancelledException {
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor)
throws IOException, CancelledException {
if (!GhidraFolder.class.isAssignableFrom(newParent.getClass())) {
throw new UnsupportedOperationException("newParent does not support copyTo");
}
@ -559,8 +568,7 @@ public class GhidraFile implements DomainFile {
* @throws CancelledException if task is cancelled
*/
void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException {
getFileData().convertToPrivateFile(
monitor != null ? monitor : TaskMonitor.DUMMY);
getFileData().convertToPrivateFile(monitor != null ? monitor : TaskMonitor.DUMMY);
}
@Override

View File

@ -24,6 +24,8 @@ import java.util.Map;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import db.DBHandle;
import db.Field;
import db.buffers.*;
@ -209,17 +211,21 @@ public class GhidraFileData {
/**
* Get a remote Ghidra URL for this domain file if available within a remote repository.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return remote Ghidra URL for this file or null
*/
URL getSharedProjectURL() {
URL getSharedProjectURL(String ref) {
synchronized (fileSystem) {
RepositoryAdapter repository = parent.getProjectFileManager().getRepository();
if (versionedFolderItem != null && repository != null) {
URL folderURL = parent.getDomainFolder().getSharedProjectURL();
try {
// Direct URL construction done so that ghidra protocol
// extension may be supported
return new URL(folderURL.toExternalForm() + name);
String spec = name;
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
}
return new URL(folderURL, spec);
}
catch (MalformedURLException e) {
// ignore
@ -229,6 +235,23 @@ public class GhidraFileData {
}
}
/**
* Get a local Ghidra URL for this domain file if available within a non-transient local
* project. A null value is returned for a transient project.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return local Ghidra URL for this file or null if transient or not applicable
*/
URL getLocalProjectURL(String ref) {
synchronized (fileSystem) {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), ref);
}
return null;
}
}
/**
* Reassign a new file-ID to resolve file-ID conflict.
* Conflicts can occur as a result of a cancelled check-out.
@ -453,8 +476,7 @@ public class GhidraFileData {
return true;
}
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (!(dobj instanceof DomainObjectAdapterDB) ||
!dobj.isChanged()) {
if (!(dobj instanceof DomainObjectAdapterDB) || !dobj.isChanged()) {
return true;
}
LockingTaskMonitor monitor = null;
@ -881,8 +903,8 @@ public class GhidraFileData {
: CheckoutType.NORMAL;
}
ItemCheckoutStatus checkout =
versionedFolderItem.checkout(checkoutType, user, ItemCheckoutStatus.getProjectPath(
projectLocator.toString(), projectLocator.isTransient()));
versionedFolderItem.checkout(checkoutType, user, ItemCheckoutStatus
.getProjectPath(projectLocator.toString(), projectLocator.isTransient()));
if (checkout == null) {
return false;
}
@ -1690,10 +1712,8 @@ public class GhidraFileData {
BufferFile bufferFile = ((DatabaseItem) item).open();
try {
newParentData.getLocalFileSystem()
.createDatabase(pathname, targetName,
FileIDFactory.createFileID(), bufferFile, null, contentType,
true,
monitor, user);
.createDatabase(pathname, targetName, FileIDFactory.createFileID(),
bufferFile, null, contentType, true, monitor, user);
}
finally {
bufferFile.dispose();
@ -1703,8 +1723,8 @@ public class GhidraFileData {
InputStream istream = ((DataFileItem) item).getInputStream();
try {
newParentData.getLocalFileSystem()
.createDataFile(pathname, targetName,
istream, null, contentType, monitor);
.createDataFile(pathname, targetName, istream, null, contentType,
monitor);
}
finally {
istream.close();
@ -1745,10 +1765,8 @@ public class GhidraFileData {
}
try {
destFolderData.getLocalFileSystem()
.createDatabase(pathname, targetName,
FileIDFactory.createFileID(), bufferFile, null, contentType, true,
monitor,
user);
.createDatabase(pathname, targetName, FileIDFactory.createFileID(),
bufferFile, null, contentType, true, monitor, user);
}
finally {
bufferFile.dispose();

View File

@ -199,8 +199,7 @@ public class GhidraFolder implements DomainFolder {
repository.getName());
}
try {
// Direct URL construction done so that ghidra protocol
// extension may be supported
// Direct URL construction done so that ghidra protocol extension may be supported
String urlStr = projectURL.toExternalForm();
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
@ -217,6 +216,15 @@ public class GhidraFolder implements DomainFolder {
}
}
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
return null;
}
@Override
public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly();

View File

@ -43,7 +43,7 @@ import ghidra.util.task.TaskMonitor;
* @param <T> {@link URLLinkObject} implementation class
*/
public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBContentHandler<T> {
public static final String URL_METADATA_KEY = "link.url";
// 16x16 link icon where link is placed in lower-left corner
@ -93,11 +93,10 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
@SuppressWarnings("unchecked")
private T getObject(FolderItem item, int version, Object consumer, TaskMonitor monitor,
boolean immutable)
throws IOException, VersionException, CancelledException {
boolean immutable) throws IOException, VersionException, CancelledException {
URL url = getURL(item);
Class<?> domainObjectClass = getDomainObjectClass();
if (domainObjectClass == null) {
throw new UnsupportedOperationException("");
@ -105,6 +104,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
GhidraURLWrappedContent wrappedContent = null;
Object content = null;
final Object transientConsumer = new Object();
try {
GhidraURLConnection c = (GhidraURLConnection) url.openConnection();
Object obj = c.getContent(); // read-only access
@ -115,22 +115,21 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
throw new IOException("Unsupported linked content");
}
wrappedContent = (GhidraURLWrappedContent) obj;
content = wrappedContent.getContent(consumer);
content = wrappedContent.getContent(transientConsumer);
if (!(content instanceof DomainFile)) {
throw new IOException("Unsupported linked content: " + content.getClass());
}
DomainFile linkedFile = (DomainFile) content;
if (!getDomainObjectClass().isAssignableFrom(linkedFile.getDomainObjectClass())) {
throw new BadLinkException(
"Expected " + getDomainObjectClass() + " but linked to " +
linkedFile.getDomainObjectClass());
throw new BadLinkException("Expected " + getDomainObjectClass() +
" but linked to " + linkedFile.getDomainObjectClass());
}
return immutable ? (T) linkedFile.getImmutableDomainObject(consumer, version, monitor)
: (T) linkedFile.getReadOnlyDomainObject(consumer, version, monitor);
}
finally {
if (content != null) {
wrappedContent.release(content, consumer);
wrappedContent.release(content, transientConsumer);
}
}
}
@ -151,8 +150,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
@Override
public final DomainObjectMergeManager getMergeManager(DomainObject resultsObj,
DomainObject sourceObj,
DomainObject originalObj, DomainObject latestObj) {
DomainObject sourceObj, DomainObject originalObj, DomainObject latestObj) {
return null;
}

View File

@ -25,6 +25,8 @@ import java.util.Map;
import javax.help.UnsupportedOperationException;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.*;
@ -100,13 +102,15 @@ class LinkedGhidraFile implements LinkedDomainFile {
}
@Override
public URL getSharedProjectURL() {
public URL getSharedProjectURL(String ref) {
URL folderURL = parent.getSharedProjectURL();
if (GhidraURL.isServerRepositoryURL(folderURL)) {
// Direct URL construction done so that ghidra protocol
// extension may be supported
try {
return new URL(folderURL.toExternalForm() + fileName);
String spec = fileName;
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
}
return new URL(folderURL, spec);
}
catch (MalformedURLException e) {
// ignore
@ -115,6 +119,15 @@ class LinkedGhidraFile implements LinkedDomainFile {
return null;
}
@Override
public URL getLocalProjectURL(String ref) {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), ref);
}
return null;
}
@Override
public ProjectLocator getProjectLocator() {
return parent.getProjectLocator();

View File

@ -107,6 +107,15 @@ class LinkedGhidraSubFolder implements LinkedDomainFolder {
return null;
}
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
return null;
}
@Override
public ProjectLocator getProjectLocator() {
return parent.getProjectLocator();

View File

@ -89,12 +89,23 @@ public interface DomainFile extends Comparable<DomainFile> {
public String getPathname();
/**
* Get a remote Ghidra URL for this domain file if available within the associated shared
* Get a remote Ghidra URL for this domain file if available within an associated shared
* project repository. A null value will be returned if shared file does not exist and
* may be returned if shared repository is not connected or a connection error occurs.
* may also be returned if shared repository is not connected or a connection error occurs.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return remote Ghidra URL for this file or null
*/
public URL getSharedProjectURL();
public URL getSharedProjectURL(String ref);
/**
* Get a local Ghidra URL for this domain file if available within the associated non-transient
* local project. A null value will be returned if project is transient.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return local Ghidra URL for this file or null if transient or not applicable
*/
public URL getLocalProjectURL(String ref);
/**
* Returns the local storage location for the project that this DomainFile belongs to.

View File

@ -35,8 +35,7 @@ import ghidra.util.task.TaskMonitor;
*/
public interface DomainFolder extends Comparable<DomainFolder> {
public static final Icon OPEN_FOLDER_ICON =
new GIcon("icon.datatree.node.domain.folder.open");
public static final Icon OPEN_FOLDER_ICON = new GIcon("icon.datatree.node.domain.folder.open");
public static final Icon CLOSED_FOLDER_ICON =
new GIcon("icon.datatree.node.domain.folder.closed");
@ -90,13 +89,21 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public String getPathname();
/**
* Get a remote Ghidra URL for this domain folder within the associated shared
* project repository. URL path will end with "/". A null value will be returned if not
* associated with a shared project.
* Get a remote Ghidra URL for this domain folder if available within an associated shared
* project repository. URL path will end with "/". A null value will be returned if shared
* folder does not exist and may also be returned if shared repository is not connected or a
* connection error occurs.
* @return remote Ghidra URL for this folder or null
*/
public URL getSharedProjectURL();
/**
* Get a local Ghidra URL for this domain file if available within the associated non-transient
* local project. A null value will be returned if project is transient.
* @return local Ghidra URL for this folder or null if transient or not applicable
*/
public URL getLocalProjectURL();
/**
* Returns true if this file is in a writable project.
* @return true if writable
@ -219,8 +226,8 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation.
*/
public DomainFolder copyTo(DomainFolder newParent, TaskMonitor monitor) throws IOException,
CancelledException;
public DomainFolder copyTo(DomainFolder newParent, TaskMonitor monitor)
throws IOException, CancelledException;
/**
* Copy this folder into the newParent folder as a link file. Restrictions:

View File

@ -102,7 +102,12 @@ public class TestDummyDomainFile implements DomainFile {
}
@Override
public URL getSharedProjectURL() {
public URL getSharedProjectURL(String ref) {
throw new UnsupportedOperationException();
}
@Override
public URL getLocalProjectURL(String ref) {
throw new UnsupportedOperationException();
}

View File

@ -88,6 +88,11 @@ public class TestDummyDomainFolder implements DomainFolder {
throw new UnsupportedOperationException();
}
@Override
public URL getLocalProjectURL() {
throw new UnsupportedOperationException();
}
@Override
public boolean isInWritableProject() {
throw new UnsupportedOperationException();