GP-3697 Added delayed ProjectFileManager disposal in support of URL use

and opening linked project files and renamed ProjectFileData to
DefaultProjectData.
This commit is contained in:
ghidra1 2023-08-11 12:49:19 -04:00
parent 5ef4b269a1
commit 3eb642885c
51 changed files with 1636 additions and 813 deletions

View file

@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
return;
}
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
BufferFile bf = null;
boolean success = false;
try {
@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
throws IOException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
FolderItem item = userFilesystem.getItem(path, name);
if (item != null) {
item.delete(-1, null);
@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
throws IOException, CancelledException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
FolderItem item = userfs.getItem(path, name);
if (item == null || !(item instanceof DatabaseItem) ||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {

View file

@ -16,12 +16,14 @@
package ghidra.framework.data;
import java.io.*;
import java.net.URL;
import java.util.*;
import docking.widgets.OptionDialog;
import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.User;
import ghidra.framework.store.*;
import ghidra.framework.store.FileSystem;
@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
/**
* Helper class to manage files within a project.
*/
public class ProjectFileManager implements ProjectData {
public class DefaultProjectData implements ProjectData {
/**Name of folder that stores user's data*/
/**
* {@code fileTrackingMap} is used to identify DefaultProjectData instances which are
* tracking specific DomainObjectAdapter instances which are open.
*/
private static Map<DomainObjectAdapter, DefaultProjectData> fileTrackingMap =
Collections.synchronizedMap(new IdentityHashMap<>());
// Names of folders that stores project data
public static final String MANGLED_DATA_FOLDER_NAME = "data";
public static final String INDEXED_DATA_FOLDER_NAME = "idata";
public static final String USER_FOLDER_NAME = "user";
@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
private RootGhidraFolderData rootFolderData;
private Map<String, DomainObjectAdapter> openDomainObjects =
new HashMap<>();
private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
private ProjectLock projectLock;
private String owner;
private int inUseCount = 0; // open file count plus active merge sessions
private boolean closed = false;
private boolean disposed = false;
/**
* Constructor for existing projects.
* @param localStorageLocator the location of the project
@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
* write lock (i.e., project in-use)
* @throws FileNotFoundException if project directory not found
*/
public ProjectFileManager(ProjectLocator localStorageLocator, boolean isInWritableProject,
public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
boolean resetOwner) throws NotOwnerException, IOException, LockException {
this.localStorageLocator = localStorageLocator;
@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
* lock (i.e., project in-use)
*/
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository,
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
boolean isInWritableProject) throws IOException, LockException {
this.localStorageLocator = localStorageLocator;
this.repository = repository;
@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
* @param versionedFileSystem an existing versioned file-system
* @throws IOException if an IO error occurs
*/
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
throws IOException {
this.localStorageLocator = new ProjectLocator(null, "Test");
owner = SystemUtilities.getUserName();
@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
/**
* Returns the owner of the project that is associated with this
* ProjectFileManager. A value of null indicates an old multiuser
* DefaultProjectData. A value of null indicates an old multiuser
* project.
* @return the owner of the project
*/
@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData {
@Override
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
TaskMonitor monitor)
throws IOException, CancelledException {
TaskMonitor monitor) throws IOException, CancelledException {
newRepository.connect();
if (!newRepository.isConnected()) {
throw new IOException("new respository not connected");
@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
long checkoutId = item.getCheckoutId();
int checkoutVersion = item.getCheckoutVersion();
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
df.getParent().getPathname(), df.getName(), checkoutId);
ItemCheckoutStatus otherCheckoutStatus =
newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
return true;
@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs
* @throws CancelledException if task cancelled
*/
@Override
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
RepositoryAdapter newRepository, TaskMonitor monitor)
throws IOException, CancelledException {
@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
* @throws IOException if IO error occurs
* @throws CancelledException if task cancelled
*/
@Override
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
throws IOException, CancelledException {
List<DomainFile> list = new ArrayList<>();
@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
}
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
TaskMonitor monitor)
throws IOException, CancelledException {
TaskMonitor monitor) throws IOException, CancelledException {
for (String name : fileSystem.getItemNames(folderPath)) {
monitor.checkCancelled();
@ -902,6 +914,30 @@ public class ProjectFileManager implements ProjectData {
}
}
@Override
public URL getSharedProjectURL() {
URL projectURL = localStorageLocator.getURL();
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
if (repository == null) {
return null;
}
// NOTE: only supports ghidra protocol without extension protocol.
// Assumes any extension protocol use would be reflected in ProjectLocator URL.
ServerInfo serverInfo = repository.getServerInfo();
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName());
}
return projectURL;
}
@Override
public URL getLocalProjectURL() {
if (!localStorageLocator.isTransient()) {
return localStorageLocator.getURL();
}
return null;
}
/**
* Returns the standard user data filename associated with the specified file ID.
* @param associatedFileID the file id
@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
}
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
synchronized (ProjectFileManager.this) {
synchronized (DefaultProjectData.this) {
startReconcileUserDataFiles();
}
});
@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
return projectDir;
}
public synchronized boolean isClosed() {
return closed;
}
public synchronized boolean isDisposed() {
return disposed;
}
@Override
public void close() {
synchronized (this) {
if (!closed) {
Msg.debug(this, "Closing ProjectData: " + projectDir);
closed = true;
}
if (inUseCount != 0) {
return; // delay dispose
}
}
dispose();
}
public void dispose() {
private synchronized void incrementInUseCount() {
++inUseCount;
}
private void decrementInUseCount() {
synchronized (this) {
if (inUseCount <= 0) {
Msg.error(this, "DefaultProjectData in-use tracking is out-of-sync: " + projectDir);
}
if (--inUseCount > 0 || !closed) {
return;
}
}
dispose();
}
/**
* Immediately dispose this project data store instance. If this project has an associated
* {@link RepositoryAdapter} it will be disconnected as well. This method should generally not
* be used directly when there may be open {@link DomainObject} instances which may rely
* on an associated server connection. The {@link #clone()} method should be used when
* open {@link DomainObject} instances may exist and should be allowed to persist until
* they are closed.
*/
protected void dispose() {
synchronized (this) {
if (disposed) {
return;
}
Msg.debug(this, "Disposing ProjectData: " + projectDir);
closed = true;
disposed = true;
if (userDataReconcileTimer != null) {
userDataReconcileTimer.stop();
}
@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
/**
* Returns the open domain object (opened for update) for the specified path.
* @param pathname the path name
* @return the domain object
* @return the domain object or null if not open
*/
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
return openDomainObjects.get(pathname);
@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
public TaskMonitor getProjectDisposalMonitor() {
return projectDisposalMonitor;
}
/**
* Signals the start of a complex merge operation.
* The {@link #mergeEnded()} must be invoked after this method invocation when the
* merge operation has completed.
*/
void mergeStarted() {
incrementInUseCount();
}
/**
* Signals the completion of a complex merge operation (see {@link #mergeStarted()}).
*/
void mergeEnded() {
decrementInUseCount();
}
/**
* Signals that a <b>non-link</b> file has been opened as the specified
* {@link DomainObjectAdapter doa} from this project data store and should be
* tracked. This will delay disposal of this object until the specified domain object is
* either closed or saved to a different project store (i.e., hand-off operation).
* It is important that this method not be invoked when opening a link-file
* since it is the referenced file being opened that must be tracked and not the
* opening of the link-file itself.
* @param doa domain object
*/
void trackDomainFileInUse(DomainObjectAdapter doa) {
DefaultProjectData projectData = fileTrackingMap.put(doa, this);
if (projectData == this) {
return; // no change in associated project
}
if (projectData != null) {
projectData.decrementInUseCount();
}
else {
doa.addCloseListener(dobj -> domainObjectClosed(dobj));
}
incrementInUseCount();
}
private static void domainObjectClosed(DomainObject dobj) {
DefaultProjectData projectData = fileTrackingMap.remove(dobj);
if (projectData != null) {
projectData.decrementInUseCount();
}
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,10 +28,10 @@ import java.util.HashMap;
*/
class DomainFileIndex implements DomainFolderChangeListener {
private ProjectFileManager projectData;
private DefaultProjectData projectData;
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
DomainFileIndex(ProjectFileManager projectData) {
DomainFileIndex(DefaultProjectData projectData) {
this.projectData = projectData;
}

View file

@ -23,14 +23,11 @@ import java.util.*;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.RepositoryItem;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.framework.store.Version;
import ghidra.framework.store.*;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.InvalidNameException;
import ghidra.util.ReadOnlyException;
@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
try {
String spec = getPathname().substring(1); // remove leading '/'
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
// Direct URL construction done so that ghidra protocol extension may be supported
String urlStr = sharedProjectURL.toExternalForm();
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
return new URL(sharedProjectURL, spec);
urlStr += getPathname();
return new URL(urlStr);
}
catch (MalformedURLException e) {
// ignore
@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
if (properties == null) {
return null;
}
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME);
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME);
String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
if (serverName == null || repoName == null) {
return null;
}
int port = Integer.parseInt(properties.getProperty(ProjectFileManager.PORT_NUMBER, "0"));
int port = Integer.parseInt(properties.getProperty(DefaultProjectData.PORT_NUMBER, "0"));
if (!ClientUtil.isConnected(serverName, port)) {
return null; // avoid initiating a server connection.
@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
return getSharedFileURL(projectURL, ref);
}
Properties properties =
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
return getSharedFileURL(properties, ref);
}
return null;

View file

@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
private volatile boolean eventsEnabled = true;
private Set<DomainObjectClosedListener> closeListeners =
new CopyOnWriteArraySet<DomainObjectClosedListener>();
private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
private ArrayList<Object> consumers;
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
private void notifyCloseListeners() {
for (DomainObjectClosedListener listener : closeListeners) {
listener.domainObjectClosed();
listener.domainObjectClosed(this);
}
closeListeners.clear();
}

View file

@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile {
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
protected ProjectFileManager fileManager;
protected DefaultProjectData projectData;
private LocalFileSystem fileSystem;
private DomainFolderChangeListener listener;
@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.listener = parent.getChangeListener();
}
public LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
void clearDomainObj() {
String path = getPathname();
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
if (doa != null && fileManager.clearDomainObject(getPathname())) {
DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
if (doa != null && projectData.clearDomainObject(getPathname())) {
listener.domainFileObjectClosed(this, doa);
}
}
@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
@Override
@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
@Override
public DomainObject getOpenedDomainObject(Object consumer) {
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
if (domainObj != null) {
if (!domainObj.addConsumer(consumer)) {
fileManager.clearDomainObject(getPathname());
projectData.clearDomainObject(getPathname());
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
}
}
@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
@Override
public void save(TaskMonitor monitor) throws IOException, CancelledException {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
throw new AssertException("Cannot save, domainObj not open");
}
@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
@Override
public boolean canSave() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
return false;
}
@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
@Override
public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
if (dobj == null) {
return new ArrayList<>();
}
@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
@Override
public boolean isChanged() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
return dobj != null && dobj.isChanged();
}
@Override
public boolean isOpen() {
return fileManager.getOpenedDomainObject(getPathname()) != null;
return projectData.getOpenedDomainObject(getPathname()) != null;
}
@Override
@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
return false;
}
GhidraFile other = (GhidraFile) obj;
if (fileManager != other.fileManager) {
if (projectData != other.projectData) {
return false;
}
return getPathname().equals(other.getPathname());
@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
@Override
public String toString() {
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname();
return projectLocator.getName() + getPathname();
}
return fileManager.getProjectLocator().getName() + ":" + getPathname();
return projectLocator.getName() + ":" + getPathname();
}
}

View file

@ -20,7 +20,6 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.FileSystem;
@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
public class GhidraFolder implements DomainFolder {
private ProjectFileManager fileManager;
private DefaultProjectData projectData;
private LocalFileSystem fileSystem;
private FileSystem versionedFileSystem;
private DomainFolderChangeListener listener;
@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolder parent;
private String name;
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
this.fileManager = fileManager;
this.fileSystem = fileManager.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem();
GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
this.projectData = projectData;
this.fileSystem = projectData.getLocalFileSystem();
this.versionedFileSystem = projectData.getVersionedFileSystem();
this.listener = listener;
this.name = FileSystem.SEPARATOR;
}
@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener();
@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
}
LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
DomainFolderChangeListener getChangeListener() {
return listener;
}
ProjectFileManager getProjectFileManager() {
return fileManager;
}
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
if (fileData == null) {
@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
GhidraFolderData getFolderData() throws FileNotFoundException {
if (parent == null) {
return fileManager.getRootFolderData();
return projectData.getRootFolderData();
}
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
if (folderData == null) {
@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
private GhidraFolderData createFolderData(String folderName) throws IOException {
synchronized (fileSystem) {
GhidraFolderData parentData =
parent == null ? fileManager.getRootFolderData() : createFolderData();
parent == null ? projectData.getRootFolderData() : createFolderData();
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
if (folderData == null) {
try {
@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
}
private GhidraFolderData createFolderData() throws IOException {
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
GhidraFolderData rootFolderData = projectData.getRootFolderData();
if (parent == null) {
return rootFolderData;
}
@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
@Override
public ProjectFileManager getProjectData() {
return fileManager;
public DefaultProjectData getProjectData() {
return projectData;
}
String getPathname(String childName) {
@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
@Override
public URL getSharedProjectURL() {
ProjectLocator projectLocator = getProjectLocator();
URL projectURL = projectLocator.getURL();
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
RepositoryAdapter repository = fileManager.getRepository();
if (repository == null) {
return null;
}
// NOTE: only supports ghidra protocol without extension protocol.
// Assumes any extension protocol use would be reflected in projectLocator URL.
ServerInfo serverInfo = repository.getServerInfo();
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName());
URL projectURL = projectData.getSharedProjectURL();
if (projectURL == null) {
return null;
}
try {
// Direct URL construction done so that ghidra protocol extension may be supported
@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
@Override
public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly();
return !fileSystem.isReadOnly();
}
@Override
@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
}
}
catch (IOException e) {
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
Msg.error(this, "file error for " + getPathname(fileName), e);
}
return null;
}
@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
return false;
}
GhidraFolder other = (GhidraFolder) obj;
if (fileManager != other.fileManager) {
if (projectData != other.projectData) {
return false;
}
return getPathname().equals(other.getPathname());
@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
@Override
public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname();
return projectData.getProjectLocator().getName() + getPathname();
}
return fileManager.getProjectLocator().getName() + ":" + getPathname();
return projectData.getProjectLocator().getName() + ":" + getPathname();
}
}

View file

@ -24,14 +24,25 @@ import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.protocol.ghidra.TransientProjectData;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FolderNotEmptyException;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* {@link GhidraFolderData} provides the managed object which represents a project folder that
* corresponds to matched folder paths across both a versioned and private
* filesystem and viewed as a single folder at the project level. This class closely mirrors the
* {@link DomainFolder} interface and is used by the {@link GhidraFolder} implementation; both of which
* represent immutable folder references. Changes made to this folder's name or path are not reflected
* in old {@link DomainFolder} instances and must be re-instantiated following such a change.
* Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an
* appropriate change listener to properly discard/reacquire such instances.
*/
class GhidraFolderData {
private ProjectFileManager fileManager;
private DefaultProjectData projectData;
/**
* Folder change listener - change events only sent if folder is visited
@ -59,17 +70,24 @@ class GhidraFolderData {
private boolean versionedFolderExists;
/**
* General constructor reserved for root folder use only
* @param fileManager
* @param listener
* General constructor reserved for root folder instantiation
* @param projectData associated project data instance
* @param listener folder change listener
*/
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
this.fileManager = fileManager;
this.fileSystem = fileManager.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem();
GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
this.projectData = projectData;
this.fileSystem = projectData.getLocalFileSystem();
this.versionedFileSystem = projectData.getVersionedFileSystem();
this.listener = listener;
}
/**
* Construct a folder instance with a specified name and a correpsonding parent folder
* @param parent parent folder
* @param name folder name
* @throws FileNotFoundException if folder not found or error occured while checking
* for its existance
*/
GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException {
if (name == null || name.isEmpty()) {
throw new FileNotFoundException("Bad folder name: blank or null");
@ -77,7 +95,7 @@ class GhidraFolderData {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener();
@ -95,36 +113,59 @@ class GhidraFolderData {
}
/**
* Returns true if folder has complete list of children
* @return true if folder has complete list of children
*/
boolean visited() {
return visited;
}
/**
* @return local file system
*/
LocalFileSystem getLocalFileSystem() {
return fileSystem;
}
/**
* @return versioned file system
*/
FileSystem getVersionedFileSystem() {
return versionedFileSystem;
}
/**
* @return local user data file system
*/
LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
return projectData.getUserFileSystem();
}
/**
* @return folder change listener
*/
DomainFolderChangeListener getChangeListener() {
return listener;
}
ProjectFileManager getProjectFileManager() {
return fileManager;
/**
* @return project data instance
*/
DefaultProjectData getProjectData() {
return projectData;
}
/**
* Get the project locator which identifies the system storage
* are for the local file system and other project related resources.
* @return local project locator
*/
ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
return projectData.getProjectLocator();
}
/**
* @return this folder's parent folder or null if this is the root folder.
*/
GhidraFolderData getParentData() {
return parent;
}
@ -143,7 +184,7 @@ class GhidraFolderData {
}
}
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy);
return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
}
if (folderPath.length() == 0) {
return this;
@ -168,10 +209,26 @@ class GhidraFolderData {
return folderData.getFolderPathData(nextPath, lazy);
}
/**
* Return this folder's name.
* @return the name
*/
String getName() {
return name;
}
/**
* Set the name on this domain folder.
* @param newName domain folder name
* @return renamed domain file (the original DomainFolder object becomes invalid since it is
* immutable)
* @throws InvalidNameException if newName contains illegal characters
* @throws DuplicateFileException if a folder named newName
* already exists in this files domain folder.
* @throws FileInUseException if any file within this folder or its descendants is
* in-use / checked-out.
* @throws IOException thrown if an IO or access error occurs.
*/
GhidraFolder setName(String newName) throws InvalidNameException, IOException {
synchronized (fileSystem) {
if (parent == null || fileSystem.isReadOnly()) {
@ -242,6 +299,10 @@ class GhidraFolderData {
return path;
}
/**
* Returns the full path name to this folder
* @return the path name
*/
String getPathname() {
if (parent == null) {
return FileSystem.SEPARATOR;
@ -254,6 +315,10 @@ class GhidraFolderData {
return path;
}
/**
* Determine if this folder contains any sub-folders or domain files.
* @return true if this folder is empty.
*/
boolean isEmpty() {
try {
refresh(false, false, null); // visited will be true upon return
@ -266,6 +331,10 @@ class GhidraFolderData {
}
}
/**
* Get the list of names for all files contained within this folder.
* @return list of file names
*/
List<String> getFileNames() {
try {
refresh(false, false, null); // visited will be true upon return
@ -277,6 +346,10 @@ class GhidraFolderData {
return new ArrayList<>(fileList);
}
/**
* Get the list of names for all subfolders contained within this folder.
* @return list of file names
*/
List<String> getFolderNames() {
try {
refresh(false, false, null); // visited will be true upon return
@ -289,9 +362,10 @@ class GhidraFolderData {
}
/**
* Update file list/cache based upon rename of file.
* If this folder has been visited listener will be notified with rename
* @param oldName
* Update file list/cache based upon rename of a file.
* If this folder has been visited the listener will be notified with rename
* @param oldFileName file name prior to rename
* @param newFileName file name after rename
*/
void fileRenamed(String oldFileName, String newFileName) {
GhidraFileData fileData;
@ -314,6 +388,14 @@ class GhidraFolderData {
}
}
/**
* Update file list/cache based upon change of parent for a file.
* If this folder or the newParent has been visited the listener will be notified with add/move
* details.
* @param newParent new parent folder
* @param oldFileName file name prior to move
* @param newFileName file name after move
*/
void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) {
GhidraFileData fileData;
synchronized (fileSystem) {
@ -340,7 +422,7 @@ class GhidraFolderData {
* underlying local or versioned file. If this folder has been visited an appropriate
* add/remove/change notification will be provided to the listener.
* NOTE: Move and Rename situations are not handled
* @param fileName
* @param fileName name of file which has changed
*/
void fileChanged(String fileName) {
synchronized (fileSystem) {
@ -389,7 +471,8 @@ class GhidraFolderData {
* visited an appropriate add/remove/change notification will be provided to the listener.
* NOTE: Care should be taken using this method as all sub-folder cache data may be disposed!
* NOTE: Move and Rename situations are not handled
* @param folderName
* @param folderName name of folder which has changed
* @throws IOException if an IO error occurs during associated refresh
*/
void folderChanged(String folderName) throws IOException {
synchronized (fileSystem) {
@ -404,7 +487,7 @@ class GhidraFolderData {
if (folderData.versionedFolderExists || folderData.folderExists) {
// preserve subfolder data
if (folderData.visited) {
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor());
folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
}
return;
}
@ -429,7 +512,7 @@ class GhidraFolderData {
/**
* Remove and dispose specified subfolder data and notify listener of removal
* if this folder has been visited
* @param folderName
* @param folderName name of folder which was removed
*/
void folderRemoved(String folderName) {
synchronized (fileSystem) {
@ -443,6 +526,9 @@ class GhidraFolderData {
}
}
/**
* Disposes the cached data for this folder and all of its children recursively.
*/
void dispose() {
visited = false;
folderList.clear();
@ -458,7 +544,7 @@ class GhidraFolderData {
// NOTE: clearing the following can cause issues since there may be some residual
// activity/use which will get a NPE
// parent = null;
// fileManager = null;
// projectData = null;
// listener = null;
}
@ -476,7 +562,7 @@ class GhidraFolderData {
* Refresh set of sub-folder names and identify added/removed folders.
* @param recursive recurse into visited subfolders if true
* @param monitor recursion task monitor - break from recursion if cancelled
* @throws IOException
* @throws IOException if an IO error occurs during the refresh
*/
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
@ -653,7 +739,7 @@ class GhidraFolderData {
* of visited state, if false refresh is lazy and will not be
* performed if a previous refresh set the visited state.
* @param monitor recursion task monitor - break from recursion if cancelled
* @throws IOException
* @throws IOException if an IO error occurs during the refresh
*/
void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException {
synchronized (fileSystem) {
@ -699,11 +785,11 @@ class GhidraFolderData {
}
/**
* Check for existence of subfolder. If this folder visited, rely on folderList
* @param fileName
* @param doRealCheck if true do not rely on fileList
* @return
* @throws IOException
* Check for existence of subfolder. If this folder has previously been visited,
* rely on the cached folderList.
* @param folderName name of folder to look for
* @return true if folder exists, else false
* @throws IOException if an IO error occurs when checking for folder's existance.
*/
boolean containsFolder(String folderName) throws IOException {
synchronized (fileSystem) {
@ -720,8 +806,8 @@ class GhidraFolderData {
/**
* Create and add new subfolder data object to cache. Data will not be created
* if folder does not exist or an IOException occurs.
* @param folderName
* @return folder data or null
* @param folderName name of folder to be added
* @return folder data or null if folder does not exist
*/
private GhidraFolderData addFolderData(String folderName) {
GhidraFolderData folderData = folderDataCache.get(folderName);
@ -739,7 +825,7 @@ class GhidraFolderData {
/**
* Get folder data for child folder specified by folderName
* @param folderName
* @param folderName name of folder
* @param lazy if true folder will not be searched for if not already discovered - in
* this case null will be returned
* @return folder data or null if not found or lazy=true and not yet discovered
@ -763,10 +849,10 @@ class GhidraFolderData {
}
/**
* Check for existence of file. If folder visited, rely on fileDataCache
* @param fileName the name of the file to check for
* @return true if this folder contains the fileName
* @throws IOException
* Check for existence of file. If folder previously visited, rely on fileDataCache
* @param fileName the name of the file to look for
* @return true if this folder contains the fileName, else false
* @throws IOException if an IO error occurs while checking for file existance
*/
public boolean containsFile(String fileName) throws IOException {
synchronized (fileSystem) {
@ -783,9 +869,9 @@ class GhidraFolderData {
/**
* Create and add new file data object to cache. Data will not be created
* if file does not exist or an IOException occurs.
* @param fileName
* @return file data or null
* @throws IOException
* @param fileName name of file
* @return file data or null if not found
* @throws IOException if an IO error occurs while checking for file existance
*/
private GhidraFileData addFileData(String fileName) throws IOException {
GhidraFileData fileData = fileDataCache.get(fileName);
@ -793,7 +879,7 @@ class GhidraFolderData {
try {
fileData = new GhidraFileData(this, fileName);
fileDataCache.put(fileName, fileData);
fileManager.updateFileIndex(fileData);
projectData.updateFileIndex(fileData);
}
catch (FileNotFoundException e) {
// ignore
@ -804,10 +890,11 @@ class GhidraFolderData {
/**
* Get file data for child specified by fileName
* @param fileName
* @param fileName name of file
* @param lazy if true file will not be searched for if not already discovered - in
* this case null will be returned
* @return file data or null if not found or lazy=true and not yet discovered
* @throws IOException if an IO error occurs while checking for file existance
*/
GhidraFileData getFileData(String fileName, boolean lazy) throws IOException {
synchronized (fileSystem) {
@ -822,58 +909,11 @@ class GhidraFolderData {
return null;
}
// // TODO: Examine!
// private void removeFolderX(String folderName) {
// folderList.remove(folderName);
// folderDataCache.remove(folderName);
// listener.domainFolderRemoved(getDomainFolder(), folderName);
// }
//
// // TODO: Examine!
// void removeFileX(String fileName) {
// fileList.remove(fileName);
// GhidraFileV2Data fileData = fileDataCache.remove(fileName);
// if (fileData != null) {
// fileData.dispose();
// }
//// TODO: May need to eliminate presence of fileID in callback
// listener.domainFileRemoved(getDomainFolder(), fileName, null /* fileID */);
// }
//
// /**
// * Handle addition of new file. If this folder has been visited, listener
// * will be notified of new file addition or change
// * @param fileName
// * @return
// */
// // TODO: Examine!
// GhidraFile fileAddedX(String fileName) {
// invalidateFile(fileName);
// GhidraFile df = getDomainFile(fileName);
// if (visited) {
// getFileData(fileName, false);
// if (fileList.add(fileName)) {
// listener.domainFileAdded(df);
// }
// else {
// listenerX.domainFileStatusChanged(df, fileID)
// }
// }
// return df;
// }
//
//
// // TODO: Examine!
// private GhidraFolder addFolderX(String folderName) {
// invalidateFolder(folderName, false);
// GhidraFolder folder = getDomainFolder(folderName);
// if (folderList.add(folderName) && visited) {
// listener.domainFolderAdded(folder);
// }
// return folder;
// }
/**
* Get the domain file in this folder with the given fileName.
* @param fileName name of file in this folder to retrieve
* @return domain file or null if there is no file in this folder with the given name.
*/
GhidraFile getDomainFile(String fileName) {
synchronized (fileSystem) {
try {
@ -888,6 +928,11 @@ class GhidraFolderData {
return null;
}
/**
* Get the domain folder in this folder with the given subfolderName.
* @param subfolderName name of subfolder in this folder to retrieve
* @return domain folder or null if there is no file in this folder with the given name.
*/
GhidraFolder getDomainFolder(String subfolderName) {
synchronized (fileSystem) {
try {
@ -902,10 +947,26 @@ class GhidraFolderData {
return null;
}
/**
* @return a {@link DomainFolder} instance which corresponds to this folder
*/
GhidraFolder getDomainFolder() {
return new GhidraFolder(parent.getDomainFolder(), name);
}
/**
* Add a domain object to this folder.
* @param fileName domain file name
* @param obj domain object to be stored
* @param monitor progress monitor
* @return domain file created as a result of adding
* the domain object to this folder
* @throws DuplicateFileException thrown if the file name already exists
* @throws InvalidNameException if name is an empty string
* or if it contains characters other than alphanumerics.
* @throws IOException if IO or access error occurs
* @throws CancelledException if the user cancels the create.
*/
GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException {
synchronized (fileSystem) {
@ -937,9 +998,12 @@ class GhidraFolderData {
throw new IOException("File creation failed for unknown reason");
}
fileManager.setDomainObject(file.getPathname(), doa);
projectData.setDomainObject(file.getPathname(), doa);
doa.setDomainFile(file);
doa.setChanged(false);
projectData.trackDomainFileInUse(doa);
listener.domainFileObjectOpenedForUpdate(file, doa);
return file;
@ -950,6 +1014,19 @@ class GhidraFolderData {
}
}
/**
* Add a new domain file to this folder.
* @param fileName domain file name
* @param packFile packed file containing domain file data
* @param monitor progress monitor
* @return domain file created as a result of adding
* the domain object to this folder
* @throws DuplicateFileException thrown if the file name already exists
* @throws InvalidNameException if name is an empty string
* or if it contains characters other than alphanumerics.
* @throws IOException if IO or access error occurs
* @throws CancelledException if the user cancels the create.
*/
GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException {
synchronized (fileSystem) {
@ -969,6 +1046,15 @@ class GhidraFolderData {
}
}
/**
* Create a subfolder within this folder.
* @param folderName sub-folder name
* @return the new folder
* @throws DuplicateFileException if a folder by this name already exists
* @throws InvalidNameException if name is an empty string of if it contains characters other
* than alphanumerics.
* @throws IOException if IO or access error occurs
*/
GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException {
synchronized (fileSystem) {
if (fileSystem.isReadOnly()) {
@ -984,6 +1070,11 @@ class GhidraFolderData {
}
}
/**
* Deletes this folder, if empty, from the local filesystem
* @throws IOException if IO or access error occurs
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
*/
void delete() throws IOException {
synchronized (fileSystem) {
if (fileSystem.isReadOnly()) {
@ -1000,6 +1091,9 @@ class GhidraFolderData {
}
}
/**
* Delete this folder from the local filesystem if empty
*/
void deleteLocalFolderIfEmpty() {
synchronized (fileSystem) {
try {
@ -1018,6 +1112,19 @@ class GhidraFolderData {
}
}
/**
* Move this folder into the newParent folder. If connected to a repository
* this moves both private and repository folders/files. If not
* connected, only private folders/files are moved.
* @param newParent new parent folder within the same project
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
* it is immutable)
* @throws DuplicateFileException if a folder with the same name
* already exists in newParent folder.
* @throws FileInUseException if this folder or one of its descendants
* contains a file which is in-use / checked-out.
* @throws IOException thrown if an IO or access error occurs.
*/
GhidraFolder moveTo(GhidraFolderData newParent) throws IOException {
synchronized (fileSystem) {
if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) {
@ -1084,11 +1191,17 @@ class GhidraFolderData {
}
}
/**
* Determine if the specified folder if an ancestor of this folder
* (i.e., parent, grand-parent, etc.).
* @param folderData folder to be checked
* @return true if the specified folder if an ancestor of this folder
*/
boolean isAncestor(GhidraFolderData folderData) {
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) {
if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
// check if projects share a common repository
RepositoryAdapter myRepository = fileManager.getRepository();
RepositoryAdapter otherRepository = folderData.fileManager.getRepository();
RepositoryAdapter myRepository = projectData.getRepository();
RepositoryAdapter otherRepository = folderData.projectData.getRepository();
if (myRepository == null || otherRepository == null ||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
!myRepository.getName().equals(otherRepository.getName())) {
@ -1105,20 +1218,30 @@ class GhidraFolderData {
return false;
}
GhidraFolder copyTo(GhidraFolderData newParentData, TaskMonitor monitor)
/**
* Copy this folder into the newParent folder.
* @param newParent new parent folder
* @param monitor the task monitor
* @return the new copied folder
* @throws DuplicateFileException if a folder or file by
* this name already exists in the newParent folder
* @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation.
*/
GhidraFolder copyTo(GhidraFolderData newParent, TaskMonitor monitor)
throws IOException, CancelledException {
synchronized (fileSystem) {
if (newParentData.fileSystem.isReadOnly()) {
if (newParent.fileSystem.isReadOnly()) {
throw new ReadOnlyException("copyTo permitted to writeable project only");
}
if (isAncestor(newParentData)) {
if (isAncestor(newParent)) {
throw new IOException("self-referencing copy not permitted");
}
GhidraFolderData newFolderData = newParentData.getFolderData(name, false);
GhidraFolderData newFolderData = newParent.getFolderData(name, false);
if (newFolderData == null) {
try {
newFolderData = newParentData.createFolder(name);
newFolderData = newParent.createFolder(name);
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
@ -1144,22 +1267,46 @@ class GhidraFolderData {
}
}
DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException {
/**
* Create a new link-file in the specified newParent which will reference this folder
* (i.e., linked-folder). Restrictions:
* <ul>
* <li>Specified newParent must reside within a different project since internal linking is
* not currently supported.</li>
* </ul>
* If this folder is associated with a temporary transient project (i.e., not a locally
* managed project) the generated link will refer to the remote folder with a remote
* Ghidra URL, otherwise a local project storage path will be used.
* @param newParent new parent folder where link-file is to be created
* @return newly created domain file (i.e., link-file) or null if link use not supported.
* @throws IOException if an IO or access error occurs.
*/
DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException {
synchronized (fileSystem) {
String linkFilename = name;
if (linkFilename == null) {
if (fileManager instanceof TransientProjectData) {
linkFilename = fileManager.getRepository().getName();
if (projectData instanceof TransientProjectData) {
linkFilename = projectData.getRepository().getName();
}
else {
linkFilename = fileManager.getProjectLocator().getName();
linkFilename = projectData.getProjectLocator().getName();
}
}
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename,
return newParent.copyAsLink(projectData, getPathname(), linkFilename,
FolderLinkContentHandler.INSTANCE);
}
}
/**
* Create a link-file within this folder. The link-file may correspond to various types of
* content (e.g., Program, Trace, Folder, etc.) based upon specified link handler.
* @param sourceProjectData referenced content project data within which specified path exists.
* @param pathname path of referenced content with source project data
* @param linkFilename name of link-file to be created within this folder.
* @param lh link file handler used to create specific link file.
* @return link-file
* @throws IOException if IO error occurs during link creation
*/
DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename,
LinkHandler<?> lh) throws IOException {
synchronized (fileSystem) {
@ -1167,7 +1314,7 @@ class GhidraFolderData {
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
}
if (sourceProjectData == fileManager) {
if (sourceProjectData == projectData) {
// internal linking not yet supported
Msg.error(this, "Internal file/folder links not yet supported");
return null;
@ -1177,13 +1324,12 @@ class GhidraFolderData {
if (sourceProjectData instanceof TransientProjectData) {
RepositoryAdapter repository = sourceProjectData.getRepository();
ServerInfo serverInfo = repository.getServerInfo();
ghidraUrl =
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
repository.getName(), pathname);
ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
serverInfo.getPortNumber(), repository.getName(), pathname);
}
else {
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
if (projectLocator.equals(fileManager.getProjectLocator())) {
if (projectLocator.equals(projectData.getProjectLocator())) {
return null; // local internal linking not supported
}
ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null);
@ -1216,6 +1362,14 @@ class GhidraFolderData {
}
}
/**
* Generate a non-conflicting file name for this folder based upon the specified preferred name.
* NOTE: This method is subject to race conditions where returned name could conflict by the
* time it is actually used.
* @param preferredName preferred file name
* @return non-conflicting file name
* @throws IOException if an IO error occurs during file checks
*/
String getTargetName(String preferredName) throws IOException {
String newName = preferredName;
int i = 1;
@ -1242,11 +1396,11 @@ class GhidraFolderData {
@Override
public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator();
ProjectLocator projectLocator = projectData.getProjectLocator();
if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname();
return projectData.getProjectLocator().getName() + getPathname();
}
return fileManager.getProjectLocator().getName() + ":" + getPathname();
return projectData.getProjectLocator().getName() + ":" + getPathname();
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,8 +19,8 @@ import ghidra.framework.model.DomainFolderChangeListener;
public class RootGhidraFolder extends GhidraFolder {
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(projectData, listener);
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem;
public class RootGhidraFolderData extends GhidraFolderData {
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
super(projectData, listener);
}
@Override
GhidraFolder getDomainFolder() {
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
return new RootGhidraFolder(getProjectData(), getChangeListener());
}
/**
* Provided for testing use only
* @param fs
* @param fs versioned file system
*/
void setVersionedFileSystem(FileSystem fs) {
versionedFileSystem = fs;

View file

@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public final static String READ_ONLY_PROPERTY = "READ_ONLY";
/**
* Get the name of the StoredObj that is associated with the data.
* Get the name of this project file
* @return the name
*/
public String getName();
@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public DomainFile setName(String newName) throws InvalidNameException, IOException;
/**
* Returns the path name to the domain object.
* Returns the full path name to this file
* @return the path name
*/
public String getPathname();
@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
public ProjectLocator getProjectLocator();
/**
* Returns content-type string
* Returns content-type string for this file
* @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT}
* or {@link ContentHandler#UNKNOWN_CONTENT}.
*/
@ -134,8 +134,11 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns changes made to versioned file by others since checkout was performed.
* NOTE: This method is unable to cope with version issues which may require an
* upgrade.
* @return change set or null
* @throws VersionException latest version was created with a newer version of software
* @throws VersionException latest version was created with a different version of software
* which prevents rapid determination of change set.
* @throws IOException if a folder item access error occurs or change set was
* produced by newer version of software and can not be read
*/
@ -426,7 +429,7 @@ public interface DomainFile extends Comparable<DomainFile> {
* @param keep if true, the private database will be renamed with a .keep
* extension.
* @throws NotConnectedException if shared project and not connected to repository
* @throws FileInUseException if this file is in-use / checked-out.
* @throws FileInUseException if this file is in-use.
* @throws IOException if file is not checked-out or an IO / access error occurs.
*/
public void undoCheckout(boolean keep) throws IOException;
@ -549,7 +552,8 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Get the list of consumers (Objects) for this domain file.
* @return empty array list if there are no consumers
* @return true if linking is supported allowing a link-file to be created which
* references this file, else false.
*/
public List<?> getConsumers();
@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns the length of this domain file. This size is the minimum disk space
* used for storing this file, but does not account for additional storage space
* used to tracks changes, etc.
* used to track changes, etc.
* @return file length
* @throws IOException if IO or access error occurs
*/

View file

@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public ProjectData getProjectData();
/**
* Returns the path name to the domain object.
* Returns the full path name to this folder
* @return the path name
*/
public String getPathname();
@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
throws InvalidNameException, IOException, CancelledException;
/**
* Create a subfolder of this folder.
* Create a subfolder within this folder.
* @param folderName sub-folder name
* @return the folder
* @throws DuplicateFileException if a folder by
* this name already exists
* @return the new folder
* @throws DuplicateFileException if a folder by this name already exists
* @throws InvalidNameException if name is an empty string of if it contains characters other
* than alphanumerics.
* @throws IOException if IO or access error occurs
@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
/**
* Deletes this folder and all of its contents
* Deletes this folder, if empty, from the local filesystem
* @throws IOException if IO or access error occurs
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
* @throws FolderNotEmptyException Thrown if this folder is not empty.
*/
public void delete() throws IOException;
/**
* Move this folder into the newParent folder. If connected to an archive
* this affects both private and repository folders and files. If not
* connected, only private folders and files are affected.
* Move this folder into the newParent folder. If connected to a repository
* this moves both private and repository folders/files. If not
* connected, only private folders/files are moved.
* @param newParent new parent folder within the same project
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
* it is immutable)
@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* Copy this folder into the newParent folder.
* @param newParent new parent folder
* @param monitor the task monitor
* @return the copied folder
* @return the new copied folder
* @throws DuplicateFileException if a folder or file by
* this name already exists in the newParent folder
* @throws IOException thrown if an IO or access error occurs.
@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
throws IOException, CancelledException;
/**
* Copy this folder into the newParent folder as a link file. Restrictions:
* Create a new link-file in the specified newParent which will reference this folder
* (i.e., linked-folder). Restrictions:
* <ul>
* <li>Specified newParent must reside within a different project since internal linking is
* not currently supported.</li>
* </ul>
* If this folder is associated with a temporary transient project (i.e., not a locally
* managed project) the generated link will refer to the remote file with a remote
* managed project) the generated link will refer to the remote folder with a remote
* Ghidra URL, otherwise a local project storage path will be used.
* @param newParent new parent folder
* @return newly created domain file or null if link use not supported.
* @param newParent new parent folder where link-file is to be created
* @return newly created domain file (i.e., link-file) or null if link use not supported.
* @throws IOException if an IO or access error occurs.
*/
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,5 +19,10 @@ package ghidra.framework.model;
* An interface that allows for a callback when a {@link DomainObject} is closed.
*/
public interface DomainObjectClosedListener {
public void domainObjectClosed();
/**
* Callback indicating that the specified {@link DomainObject} has been closed.
* @param dobj domain object
*/
public void domainObjectClosed(DomainObject dobj);
}

View file

@ -191,8 +191,10 @@ public interface ProjectData {
TaskMonitor monitor) throws IOException, CancelledException;
/**
* Close the project storage associated with this project data object.
* NOTE: This should not be invoked if this object is utilized by a Project instance.
* Initiate disposal of this project data object. Any files already open will delay
* disposal until they are closed.
* NOTE: This should only be invoked by the controlling object which created/opened this
* instance to avoid premature disposal.
*/
public void close();
@ -209,4 +211,18 @@ public interface ProjectData {
*/
public void testValidName(String name, boolean isPath) throws InvalidNameException;
/**
* Generate a repository URL which corresponds to this project data if applicable.
* Local private projects will return null;
* @return repository URL which corresponds to this project data or null if not applicable.
*/
public URL getSharedProjectURL();
/**
* Generate a local URL which corresponds to this project data if applicable.
* Remote transient project data will return null;
* @return local URL which corresponds to this project data or null if not applicable.
*/
public URL getLocalProjectURL();
}

View file

@ -25,7 +25,7 @@ import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.data.TransientDataManager;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
@ -58,7 +58,7 @@ public class DefaultProject implements Project {
private DefaultProjectManager projectManager;
private ProjectLocator projectLocator;
private ProjectFileManager fileMgr;
private DefaultProjectData projectData;
private ToolManagerImpl toolManager;
private boolean changed; // flag for whether the project configuration has changed
@ -66,7 +66,7 @@ public class DefaultProject implements Project {
private Map<String, SaveState> dataMap = new HashMap<>();
private Map<String, ToolTemplate> projectConfigMap = new HashMap<>();
private Map<URL, ProjectFileManager> otherViews = new HashMap<>();
private Map<URL, DefaultProjectData> otherViewsMap = new HashMap<>();
private Set<URL> visibleViews = new HashSet<>();
private WeakSet<ProjectViewListener> viewListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
@ -86,21 +86,10 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
this.projectLocator = projectLocator;
boolean success = false;
try {
Msg.info(this, "Creating project: " + projectLocator.toString());
fileMgr = new ProjectFileManager(projectLocator, repository, true);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
Msg.info(this, "Creating project: " + projectLocator.toString());
projectData = new DefaultProjectData(projectLocator, repository, true);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
initializeNewProject();
}
@ -122,28 +111,18 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
this.projectLocator = projectLocator;
boolean success = false;
try {
Msg.info(this, "Opening project: " + projectLocator.toString());
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
Msg.info(this, "Opening project: " + projectLocator.toString());
projectData = new DefaultProjectData(projectLocator, true, resetOwner);
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
}
/**
* Constructor for opening a URL-based project
*
* @param connection project connection
* @param projectManager the manager of this project
* @param connection project URL connection (not previously used)
* @throws IOException if I/O error occurs.
*/
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
@ -151,27 +130,17 @@ public class DefaultProject implements Project {
this.projectManager = projectManager;
boolean success = false;
try {
Msg.info(this, "Opening project/repository: " + connection.getURL());
fileMgr = (ProjectFileManager) connection.getProjectData();
if (fileMgr == null) {
throw new IOException("Failed to open project/repository: " + connection.getURL());
}
Msg.info(this, "Opening project/repository: " + connection.getURL());
projectData = (DefaultProjectData) connection.getProjectData();
if (projectData == null) {
throw new IOException("Failed to open project/repository: " + connection.getURL());
}
projectLocator = fileMgr.getProjectLocator();
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
success = true;
}
finally {
if (!success) {
if (fileMgr != null) {
fileMgr.dispose();
}
}
projectLocator = projectData.getProjectLocator();
if (!SystemUtilities.isInHeadlessMode()) {
toolManager = new ToolManagerImpl(this);
}
initializeNewProject();
}
@ -300,20 +269,20 @@ public class DefaultProject implements Project {
return null;
}
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData();
DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
if (projectData == null) {
throw new IOException(
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
}
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
otherViews.put(url, projectData);
otherViewsMap.put(url, projectData);
return projectData;
}
@Override
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
synchronized (otherViews) {
synchronized (otherViewsMap) {
if (isClosed) {
throw new IOException("project is closed");
}
@ -322,7 +291,7 @@ public class DefaultProject implements Project {
throw new IOException("Invalid Ghidra URL specified: " + url);
}
ProjectData projectData = otherViews.get(url);
ProjectData projectData = otherViewsMap.get(url);
if (projectData == null) {
projectData = openProjectView(url);
}
@ -340,13 +309,13 @@ public class DefaultProject implements Project {
*/
@Override
public void removeProjectView(URL url) {
synchronized (otherViews) {
ProjectFileManager dataMgr = otherViews.remove(url);
synchronized (otherViewsMap) {
DefaultProjectData dataMgr = otherViewsMap.remove(url);
if (dataMgr != null) {
if (visibleViews.remove(url)) {
notifyVisibleViewRemoved(url);
}
dataMgr.dispose();
dataMgr.close();
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
changed = true;
}
@ -401,22 +370,20 @@ public class DefaultProject implements Project {
@Override
public RepositoryAdapter getRepository() {
return fileMgr.getRepository();
return projectData.getRepository();
}
@Override
public void close() {
synchronized (otherViews) {
synchronized (otherViewsMap) {
isClosed = true;
Iterator<ProjectFileManager> iter = otherViews.values().iterator();
while (iter.hasNext()) {
ProjectFileManager dataMgr = iter.next();
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
if (dataMgr != null) {
dataMgr.dispose();
dataMgr.close();
}
}
otherViews.clear();
otherViewsMap.clear();
}
try {
@ -429,7 +396,7 @@ public class DefaultProject implements Project {
}
}
finally {
fileMgr.dispose();
projectData.close();
}
}
@ -449,7 +416,7 @@ public class DefaultProject implements Project {
@Override
public void restore() {
// if there is a saved project, restore it
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
String errorMsg = null;
Throwable error = null;
try {
@ -593,7 +560,7 @@ public class DefaultProject implements Project {
try {
// save tool state
root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
OutputStream os = new FileOutputStream(saveFile);
Document doc = new Document(root);
XMLOutputter xmlOut = new GenericXMLOutputter();
@ -629,15 +596,14 @@ public class DefaultProject implements Project {
@Override
public List<DomainFile> getOpenData() {
ArrayList<DomainFile> openFiles = new ArrayList<>();
fileMgr.findOpenFiles(openFiles);
projectData.findOpenFiles(openFiles);
ProjectData[] viewedProjs = getViewedProjectData();
for (ProjectData viewedProj : viewedProjs) {
((ProjectFileManager) viewedProj).findOpenFiles(openFiles);
((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
}
List<DomainFile> list = new ArrayList<>();
TransientDataManager.getTransients(list);
for (int i = 0; i < list.size(); i++) {
DomainFile df = list.get(i);
for (DomainFile df : list) {
if (df != null && df.isOpen()) {
openFiles.add(df);
}
@ -646,8 +612,8 @@ public class DefaultProject implements Project {
}
@Override
public ProjectFileManager getProjectData() {
return fileMgr;
public DefaultProjectData getProjectData() {
return projectData;
}
@Override
@ -662,12 +628,12 @@ public class DefaultProject implements Project {
@Override
public ProjectData getProjectData(ProjectLocator locator) {
if (locator.equals(fileMgr.getProjectLocator())) {
return fileMgr;
if (locator.equals(projectData.getProjectLocator())) {
return projectData;
}
synchronized (otherViews) {
for (ProjectData data : otherViews.values()) {
synchronized (otherViewsMap) {
for (ProjectData data : otherViewsMap.values()) {
if (locator.equals(data.getProjectLocator())) {
return data;
}
@ -680,30 +646,30 @@ public class DefaultProject implements Project {
@Override
public ProjectData getProjectData(URL url) {
if (projectLocator.getURL().equals(url)) {
return fileMgr;
return projectData;
}
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
if (remoteURL != null) {
remoteURL = GhidraURL.getProjectURL(url);
}
if (remoteURL.equals(url)) {
return fileMgr;
return projectData;
}
synchronized (otherViews) {
return otherViews.get(url);
synchronized (otherViewsMap) {
return otherViewsMap.get(url);
}
}
@Override
public ProjectData[] getViewedProjectData() {
synchronized (otherViews) {
synchronized (otherViewsMap) {
// only return visible viewed project
List<ProjectData> list = new ArrayList<>();
for (URL url : otherViews.keySet()) {
for (URL url : otherViewsMap.keySet()) {
if (visibleViews.contains(url)) {
list.add(otherViews.get(url));
list.add(otherViewsMap.get(url));
}
}
@ -715,11 +681,9 @@ public class DefaultProject implements Project {
@Override
public void releaseFiles(Object consumer) {
fileMgr.releaseDomainFiles(consumer);
synchronized (otherViews) {
Iterator<ProjectFileManager> it = otherViews.values().iterator();
while (it.hasNext()) {
ProjectFileManager mgr = it.next();
projectData.releaseDomainFiles(consumer);
synchronized (otherViewsMap) {
for (DefaultProjectData mgr : otherViewsMap.values()) {
mgr.releaseDomainFiles(consumer);
}
}

View file

@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
}
catch (RepositoryNotFoundException e) {
statusCode = StatusCode.NOT_FOUND;
return statusCode;
}
}
else if (!repositoryServerAdapter.isCancelled()) {

View file

@ -21,7 +21,7 @@ import java.net.URL;
import ghidra.framework.client.NotConnectedException;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
import ghidra.framework.store.LockException;
@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
* @return project data instance or null if project not found
* @throws IOException if IO error occurs
*/
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException {
DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
if (connect(readOnlyAccess) != StatusCode.OK) {
return null;
}
try {
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false);
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
}
catch (NotOwnerException | ReadOnlyException e) {
statusCode = StatusCode.UNAUTHORIZED;

View file

@ -19,7 +19,7 @@ import java.io.*;
import java.net.*;
import ghidra.framework.client.*;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectData;
import ghidra.util.exception.AssertException;
@ -69,38 +69,6 @@ public class GhidraURLConnection extends URLConnection {
}
}
// TODO: consider implementing request and response headers
// /**
// * Ghidra Status-Code 200: OK.
// */
// public static final int GHIDRA_OK = 200;
//
// /**
// * Ghidra Status-Code 401: Unauthorized.
// * This response code includes a variety of connection errors
// * which are reported/logged by the Ghidra Server support code.
// */
// public static final int GHIDRA_UNAUTHORIZED = 401;
//
// /**
// * Ghidra Status-Code 404: Not Found.
// */
// public static final int GHIDRA_NOT_FOUND = 404;
//
// /**
// * Ghidra Status-Code 423: Locked
// * Caused by attempt to open local project data with write-access when project is
// * already opened and locked.
// */
// public static final int GHIDRA_LOCKED = 423;
//
// /**
// * Ghidra Status-Code 503: Unavailable
// * Caused by other connection failure
// */
// public static final int GHIDRA_UNAVAILABLE = 503;
/**
* Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object.
* @see GhidraURLWrappedContent
@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
private GhidraProtocolConnector protocolConnector;
private ProjectFileManager projectData;
private DefaultProjectData projectData;
private Object refObject;
private boolean readOnly = true;
@ -270,12 +238,17 @@ public class GhidraURLConnection extends URLConnection {
}
/**
* If URL connects and corresponds to a valid repository, this method
* If URL connects and corresponds to a valid repository or local project, this method
* may be used to obtain the associated ProjectData object. The caller is
* responsible for closing the returned project data when no longer in-use,
* failure to do so may prevent release of repository handle to server.
* Only a single call to this method is permitted.
* @return transient project data or null if unavailable
* responsible for properly {@link ProjectData#close() closing} the returned project data
* instance when no longer in-use, failure to do so may prevent release of repository handle
* to server until process exits. It is important that {@link ProjectData#close()} is
* invoked once, and only once, per call to this method to ensure project "use" tracking
* is properly maintained. Improperly invoking the close method on a shared transient
* {@link ProjectData} instance may cause the underlying storage to be prematurely
* disposed.
*
* @return project data which corresponds to this connection or null if unavailable
* @throws IOException if an IO error occurs
*/
public ProjectData getProjectData() throws IOException {

View file

@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.*;
import ghidra.util.InvalidNameException;
@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
private List<Object> consumers = new ArrayList<Object>();
private ProjectData projectData;
private DefaultProjectData projectData;
private Object refObject;
public GhidraURLWrappedContent(GhidraURLConnection c) {
@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
/**
* Close associated {@link ProjectData} when all consumers have released wrapped object.
* Underlying project data instance may remain active until all open project files have been
* released/closed.
*/
private void closeProjectData() {
if (projectData != null) {
@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
refObject = null;
}
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException,
IOException {
private DomainFolder getExplicitFolder(String folderPath)
throws InvalidNameException, IOException {
DomainFolder folder = projectData.getRootFolder();
for (String name : folderPath.substring(1).split("/")) {
DomainFolder subfolder = folder.getFolder(name);
@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
return;
}
projectData = c.getProjectData();
projectData = (DefaultProjectData) c.getProjectData();
String folderItemName = c.getFolderItemName();
String folderPath = c.getFolderPath();

View file

@ -16,10 +16,11 @@
package ghidra.framework.protocol.ghidra;
import java.io.IOException;
import java.net.URL;
import generic.timer.GhidraSwinglessTimer;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.data.DefaultProjectData;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.remote.RepositoryHandle;
import ghidra.framework.store.LockException;
@ -27,7 +28,7 @@ import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import utilities.util.FileUtilities;
public class TransientProjectData extends ProjectFileManager {
public class TransientProjectData extends DefaultProjectData {
private TransientProjectManager dataMgr;
final RepositoryInfo repositoryInfo;
@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
}
stopCleanupTimer();
disposed = true;
}
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " +
getProjectLocator().getProjectDir());
String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
getProjectLocator().getProjectDir() + ", URL: " + repositoryInfo.getURL();
if (instanceUseCount != 0) {
Msg.error(this, "Premature removal of active" + msgTail);
}
else {
Msg.debug(this, "Removing idle" + msgTail);
}
}
dataMgr.cleanupProjectData(repositoryInfo, this);
super.dispose(); // disconnects repository
// TODO: There could still be open files if they have not been properly released/closed !!
// Remove temporary project storage
// NOTE: This could be affected by project files which still remain open
ProjectLocator locator = getProjectLocator();
FileUtilities.deleteDir(locator.getProjectDir());
locator.getMarkerFile().delete();
}
@Override
public void dispose() {
public void close() {
// prevent normal disposal - rely on finalizer and shutdown hook
synchronized (cleanupTimer) {
if (instanceUseCount == 0) {
@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
}
@Override
protected void finalize() throws Throwable {
try {
forcedDispose();
}
catch (Throwable t) {
// ignore errors during finalize
}
super.finalize();
protected void dispose() {
// rely on forcedDispose to invoke super.dispose()
}
@Override
public URL getSharedProjectURL() {
return repositoryInfo.getURL();
}
@Override
public URL getLocalProjectURL() {
return null;
}
}

View file

@ -71,8 +71,9 @@ public class TransientProjectManager {
}
private TransientProjectManager() {
Runtime.getRuntime().addShutdownHook(
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook"));
Runtime.getRuntime()
.addShutdownHook(new Thread((Runnable) () -> dispose(),
"TransientProjectManager Shutdown Hook"));
}
/**
@ -80,8 +81,6 @@ public class TransientProjectManager {
* connections. WARNING: This method intended for testing only.
*/
public synchronized void dispose() {
// TODO: server handles may be shared with non-transient projects
TransientProjectData[] projectDataArray =
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
for (TransientProjectData projectData : projectDataArray) {

View file

@ -15,17 +15,16 @@
*/
package ghidra.framework.task;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.DomainObjectClosedListener;
import ghidra.framework.model.UndoableDomainObject;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import generic.concurrent.GThreadPool;
import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
/**
* Class for managing a queue of tasks to be executed, one at a time, in priority order. All the
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
@ -95,7 +94,8 @@ public class GTaskManager {
domainObject.addCloseListener(new DomainObjectClosedListener() {
@Override
public void domainObjectClosed() {
public void domainObjectClosed(DomainObject dobj) {
// assert dobj == domainObj
GTaskManagerFactory.domainObjectClosed(domainObject);
domainObject = null;
}
@ -107,7 +107,7 @@ public class GTaskManager {
*
* @param task the task to be run.
* @param priority the priority of the task. Lower numbers are run before higher numbers.
* @param useCurrentGroup. If true, this task will be rolled into the current transaction group
* @param useCurrentGroup If true, this task will be rolled into the current transaction group
* if one exists. If false, any open transaction
* will be closed and a new transaction will be opened before
* this task is run.
@ -680,7 +680,8 @@ public class GTaskManager {
taskListener.taskCompleted(task, result);
}
catch (Throwable unexpected) {
Msg.error(this, "Unexpected exception notifying listener of task completed", unexpected);
Msg.error(this, "Unexpected exception notifying listener of task completed",
unexpected);
}
}
@ -705,7 +706,8 @@ public class GTaskManager {
taskListener.taskScheduled(scheduledTask);
}
catch (Throwable unexpected) {
Msg.error(this, "Unexpected exception notifying listener of task scheduled", unexpected);
Msg.error(this, "Unexpected exception notifying listener of task scheduled",
unexpected);
}
}

View file

@ -16,6 +16,7 @@
package ghidra.framework.model;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import ghidra.framework.client.RepositoryAdapter;
@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
return null;
}
@Override
public URL getSharedProjectURL() {
// stub
return null;
}
@Override
public URL getLocalProjectURL() {
// stub
return null;
}
@Override
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
// stub