mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-3697 Added delayed ProjectFileManager disposal in support of URL use
and opening linked project files and renamed ProjectFileData to DefaultProjectData.
This commit is contained in:
parent
5ef4b269a1
commit
3eb642885c
51 changed files with 1636 additions and 813 deletions
|
@ -73,7 +73,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
|||
return;
|
||||
}
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||
BufferFile bf = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
|
@ -109,7 +109,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
|||
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userFilesystem)
|
||||
throws IOException {
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedItem.getFileID());
|
||||
FolderItem item = userFilesystem.getItem(path, name);
|
||||
if (item != null) {
|
||||
item.delete(-1, null);
|
||||
|
@ -130,7 +130,7 @@ public abstract class DBWithUserDataContentHandler<T extends DomainObjectAdapter
|
|||
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
String path = "/";
|
||||
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
|
||||
String name = DefaultProjectData.getUserDataFilename(associatedFileID);
|
||||
FolderItem item = userfs.getItem(path, name);
|
||||
if (item == null || !(item instanceof DatabaseItem) ||
|
||||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
package ghidra.framework.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.timer.GhidraSwinglessTimer;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
|
@ -37,9 +39,16 @@ import utilities.util.FileUtilities;
|
|||
/**
|
||||
* Helper class to manage files within a project.
|
||||
*/
|
||||
public class ProjectFileManager implements ProjectData {
|
||||
public class DefaultProjectData implements ProjectData {
|
||||
|
||||
/**Name of folder that stores user's data*/
|
||||
/**
|
||||
* {@code fileTrackingMap} is used to identify DefaultProjectData instances which are
|
||||
* tracking specific DomainObjectAdapter instances which are open.
|
||||
*/
|
||||
private static Map<DomainObjectAdapter, DefaultProjectData> fileTrackingMap =
|
||||
Collections.synchronizedMap(new IdentityHashMap<>());
|
||||
|
||||
// Names of folders that stores project data
|
||||
public static final String MANGLED_DATA_FOLDER_NAME = "data";
|
||||
public static final String INDEXED_DATA_FOLDER_NAME = "idata";
|
||||
public static final String USER_FOLDER_NAME = "user";
|
||||
|
@ -77,14 +86,17 @@ public class ProjectFileManager implements ProjectData {
|
|||
|
||||
private RootGhidraFolderData rootFolderData;
|
||||
|
||||
private Map<String, DomainObjectAdapter> openDomainObjects =
|
||||
new HashMap<>();
|
||||
private Map<String, DomainObjectAdapter> openDomainObjects = new HashMap<>();
|
||||
|
||||
private TaskMonitorAdapter projectDisposalMonitor = new TaskMonitorAdapter();
|
||||
|
||||
private ProjectLock projectLock;
|
||||
private String owner;
|
||||
|
||||
private int inUseCount = 0; // open file count plus active merge sessions
|
||||
private boolean closed = false;
|
||||
private boolean disposed = false;
|
||||
|
||||
/**
|
||||
* Constructor for existing projects.
|
||||
* @param localStorageLocator the location of the project
|
||||
|
@ -96,7 +108,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
* write lock (i.e., project in-use)
|
||||
* @throws FileNotFoundException if project directory not found
|
||||
*/
|
||||
public ProjectFileManager(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
||||
boolean resetOwner) throws NotOwnerException, IOException, LockException {
|
||||
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
|
@ -142,7 +154,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
|
||||
* lock (i.e., project in-use)
|
||||
*/
|
||||
public ProjectFileManager(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||
boolean isInWritableProject) throws IOException, LockException {
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
this.repository = repository;
|
||||
|
@ -170,7 +182,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
* @param versionedFileSystem an existing versioned file-system
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
||||
DefaultProjectData(LocalFileSystem fileSystem, FileSystem versionedFileSystem)
|
||||
throws IOException {
|
||||
this.localStorageLocator = new ProjectLocator(null, "Test");
|
||||
owner = SystemUtilities.getUserName();
|
||||
|
@ -530,7 +542,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
|
||||
/**
|
||||
* Returns the owner of the project that is associated with this
|
||||
* ProjectFileManager. A value of null indicates an old multiuser
|
||||
* DefaultProjectData. A value of null indicates an old multiuser
|
||||
* project.
|
||||
* @return the owner of the project
|
||||
*/
|
||||
|
@ -731,9 +743,8 @@ public class ProjectFileManager implements ProjectData {
|
|||
|
||||
@Override
|
||||
public void updateRepositoryInfo(RepositoryAdapter newRepository, boolean force,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
||||
newRepository.connect();
|
||||
if (!newRepository.isConnected()) {
|
||||
throw new IOException("new respository not connected");
|
||||
|
@ -761,8 +772,8 @@ public class ProjectFileManager implements ProjectData {
|
|||
long checkoutId = item.getCheckoutId();
|
||||
int checkoutVersion = item.getCheckoutVersion();
|
||||
|
||||
ItemCheckoutStatus otherCheckoutStatus = newRepository.getCheckout(
|
||||
df.getParent().getPathname(), df.getName(), checkoutId);
|
||||
ItemCheckoutStatus otherCheckoutStatus =
|
||||
newRepository.getCheckout(df.getParent().getPathname(), df.getName(), checkoutId);
|
||||
|
||||
if (!newRepository.getUser().getName().equals(otherCheckoutStatus.getUser())) {
|
||||
return true;
|
||||
|
@ -793,6 +804,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
@Override
|
||||
public boolean hasInvalidCheckouts(List<DomainFile> checkoutList,
|
||||
RepositoryAdapter newRepository, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
@ -856,6 +868,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
* @throws IOException if IO error occurs
|
||||
* @throws CancelledException if task cancelled
|
||||
*/
|
||||
@Override
|
||||
public List<DomainFile> findCheckedOutFiles(TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
|
@ -864,8 +877,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
}
|
||||
|
||||
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
|
||||
TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
TaskMonitor monitor) throws IOException, CancelledException {
|
||||
|
||||
for (String name : fileSystem.getItemNames(folderPath)) {
|
||||
monitor.checkCancelled();
|
||||
|
@ -902,6 +914,30 @@ public class ProjectFileManager implements ProjectData {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
URL projectURL = localStorageLocator.getURL();
|
||||
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||
if (repository == null) {
|
||||
return null;
|
||||
}
|
||||
// NOTE: only supports ghidra protocol without extension protocol.
|
||||
// Assumes any extension protocol use would be reflected in ProjectLocator URL.
|
||||
ServerInfo serverInfo = repository.getServerInfo();
|
||||
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
||||
repository.getName());
|
||||
}
|
||||
return projectURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
if (!localStorageLocator.isTransient()) {
|
||||
return localStorageLocator.getURL();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard user data filename associated with the specified file ID.
|
||||
* @param associatedFileID the file id
|
||||
|
@ -934,7 +970,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
}
|
||||
|
||||
userDataReconcileTimer = new GhidraSwinglessTimer(USER_DATA_RECONCILE_DELAY_MS, () -> {
|
||||
synchronized (ProjectFileManager.this) {
|
||||
synchronized (DefaultProjectData.this) {
|
||||
startReconcileUserDataFiles();
|
||||
}
|
||||
});
|
||||
|
@ -1184,14 +1220,64 @@ public class ProjectFileManager implements ProjectData {
|
|||
return projectDir;
|
||||
}
|
||||
|
||||
public synchronized boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public synchronized boolean isDisposed() {
|
||||
return disposed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (this) {
|
||||
if (!closed) {
|
||||
Msg.debug(this, "Closing ProjectData: " + projectDir);
|
||||
closed = true;
|
||||
}
|
||||
if (inUseCount != 0) {
|
||||
return; // delay dispose
|
||||
}
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
private synchronized void incrementInUseCount() {
|
||||
++inUseCount;
|
||||
}
|
||||
|
||||
private void decrementInUseCount() {
|
||||
synchronized (this) {
|
||||
if (inUseCount <= 0) {
|
||||
Msg.error(this, "DefaultProjectData in-use tracking is out-of-sync: " + projectDir);
|
||||
}
|
||||
if (--inUseCount > 0 || !closed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately dispose this project data store instance. If this project has an associated
|
||||
* {@link RepositoryAdapter} it will be disconnected as well. This method should generally not
|
||||
* be used directly when there may be open {@link DomainObject} instances which may rely
|
||||
* on an associated server connection. The {@link #clone()} method should be used when
|
||||
* open {@link DomainObject} instances may exist and should be allowed to persist until
|
||||
* they are closed.
|
||||
*/
|
||||
protected void dispose() {
|
||||
|
||||
synchronized (this) {
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Msg.debug(this, "Disposing ProjectData: " + projectDir);
|
||||
|
||||
closed = true;
|
||||
disposed = true;
|
||||
|
||||
if (userDataReconcileTimer != null) {
|
||||
userDataReconcileTimer.stop();
|
||||
}
|
||||
|
@ -1255,7 +1341,7 @@ public class ProjectFileManager implements ProjectData {
|
|||
/**
|
||||
* Returns the open domain object (opened for update) for the specified path.
|
||||
* @param pathname the path name
|
||||
* @return the domain object
|
||||
* @return the domain object or null if not open
|
||||
*/
|
||||
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
|
||||
return openDomainObjects.get(pathname);
|
||||
|
@ -1298,4 +1384,55 @@ public class ProjectFileManager implements ProjectData {
|
|||
public TaskMonitor getProjectDisposalMonitor() {
|
||||
return projectDisposalMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the start of a complex merge operation.
|
||||
* The {@link #mergeEnded()} must be invoked after this method invocation when the
|
||||
* merge operation has completed.
|
||||
*/
|
||||
void mergeStarted() {
|
||||
incrementInUseCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the completion of a complex merge operation (see {@link #mergeStarted()}).
|
||||
*/
|
||||
void mergeEnded() {
|
||||
decrementInUseCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that a <b>non-link</b> file has been opened as the specified
|
||||
* {@link DomainObjectAdapter doa} from this project data store and should be
|
||||
* tracked. This will delay disposal of this object until the specified domain object is
|
||||
* either closed or saved to a different project store (i.e., hand-off operation).
|
||||
* It is important that this method not be invoked when opening a link-file
|
||||
* since it is the referenced file being opened that must be tracked and not the
|
||||
* opening of the link-file itself.
|
||||
* @param doa domain object
|
||||
*/
|
||||
void trackDomainFileInUse(DomainObjectAdapter doa) {
|
||||
DefaultProjectData projectData = fileTrackingMap.put(doa, this);
|
||||
if (projectData == this) {
|
||||
return; // no change in associated project
|
||||
}
|
||||
|
||||
if (projectData != null) {
|
||||
projectData.decrementInUseCount();
|
||||
}
|
||||
else {
|
||||
doa.addCloseListener(dobj -> domainObjectClosed(dobj));
|
||||
}
|
||||
|
||||
incrementInUseCount();
|
||||
}
|
||||
|
||||
private static void domainObjectClosed(DomainObject dobj) {
|
||||
|
||||
DefaultProjectData projectData = fileTrackingMap.remove(dobj);
|
||||
if (projectData != null) {
|
||||
projectData.decrementInUseCount();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -29,10 +28,10 @@ import java.util.HashMap;
|
|||
*/
|
||||
class DomainFileIndex implements DomainFolderChangeListener {
|
||||
|
||||
private ProjectFileManager projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
|
||||
|
||||
DomainFileIndex(ProjectFileManager projectData) {
|
||||
DomainFileIndex(DefaultProjectData projectData) {
|
||||
this.projectData = projectData;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,14 +23,11 @@ import java.util.*;
|
|||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.RepositoryItem;
|
||||
import ghidra.framework.store.ItemCheckoutStatus;
|
||||
import ghidra.framework.store.Version;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.db.PackedDatabase;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
|
@ -120,11 +117,13 @@ public class DomainFileProxy implements DomainFile {
|
|||
|
||||
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
|
||||
try {
|
||||
String spec = getPathname().substring(1); // remove leading '/'
|
||||
if (!StringUtils.isEmpty(ref)) {
|
||||
spec += "#" + ref;
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
String urlStr = sharedProjectURL.toExternalForm();
|
||||
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
|
||||
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||
}
|
||||
return new URL(sharedProjectURL, spec);
|
||||
urlStr += getPathname();
|
||||
return new URL(urlStr);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
|
@ -136,12 +135,12 @@ public class DomainFileProxy implements DomainFile {
|
|||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
String serverName = properties.getProperty(ProjectFileManager.SERVER_NAME);
|
||||
String repoName = properties.getProperty(ProjectFileManager.REPOSITORY_NAME);
|
||||
String serverName = properties.getProperty(DefaultProjectData.SERVER_NAME);
|
||||
String repoName = properties.getProperty(DefaultProjectData.REPOSITORY_NAME);
|
||||
if (serverName == null || repoName == null) {
|
||||
return null;
|
||||
}
|
||||
int port = Integer.parseInt(properties.getProperty(ProjectFileManager.PORT_NUMBER, "0"));
|
||||
int port = Integer.parseInt(properties.getProperty(DefaultProjectData.PORT_NUMBER, "0"));
|
||||
|
||||
if (!ClientUtil.isConnected(serverName, port)) {
|
||||
return null; // avoid initiating a server connection.
|
||||
|
@ -187,7 +186,7 @@ public class DomainFileProxy implements DomainFile {
|
|||
return getSharedFileURL(projectURL, ref);
|
||||
}
|
||||
Properties properties =
|
||||
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
|
||||
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
|
||||
return getSharedFileURL(properties, ref);
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -54,8 +54,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
|||
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
|
||||
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
|
||||
private volatile boolean eventsEnabled = true;
|
||||
private Set<DomainObjectClosedListener> closeListeners =
|
||||
new CopyOnWriteArraySet<DomainObjectClosedListener>();
|
||||
private Set<DomainObjectClosedListener> closeListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
private ArrayList<Object> consumers;
|
||||
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
|
||||
|
@ -210,7 +209,7 @@ public abstract class DomainObjectAdapter implements DomainObject {
|
|||
|
||||
private void notifyCloseListeners() {
|
||||
for (DomainObjectClosedListener listener : closeListeners) {
|
||||
listener.domainObjectClosed();
|
||||
listener.domainObjectClosed(this);
|
||||
}
|
||||
closeListeners.clear();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
|
||||
|
||||
protected ProjectFileManager fileManager;
|
||||
protected DefaultProjectData projectData;
|
||||
|
||||
private LocalFileSystem fileSystem;
|
||||
private DomainFolderChangeListener listener;
|
||||
|
@ -45,13 +45,13 @@ public class GhidraFile implements DomainFile {
|
|||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.listener = parent.getChangeListener();
|
||||
}
|
||||
|
||||
public LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
private GhidraFileData getFileData() throws FileNotFoundException, IOException {
|
||||
|
@ -97,8 +97,8 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
void clearDomainObj() {
|
||||
String path = getPathname();
|
||||
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
|
||||
if (doa != null && fileManager.clearDomainObject(getPathname())) {
|
||||
DomainObjectAdapter doa = projectData.getOpenedDomainObject(path);
|
||||
if (doa != null && projectData.clearDomainObject(getPathname())) {
|
||||
listener.domainFileObjectClosed(this, doa);
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public ProjectLocator getProjectLocator() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -215,10 +215,10 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public DomainObject getOpenedDomainObject(Object consumer) {
|
||||
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter domainObj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (domainObj != null) {
|
||||
if (!domainObj.addConsumer(consumer)) {
|
||||
fileManager.clearDomainObject(getPathname());
|
||||
projectData.clearDomainObject(getPathname());
|
||||
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public void save(TaskMonitor monitor) throws IOException, CancelledException {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
throw new AssertException("Cannot save, domainObj not open");
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public boolean canSave() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -573,7 +573,7 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public ArrayList<?> getConsumers() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
if (dobj == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
@ -582,13 +582,13 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public boolean isChanged() {
|
||||
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
|
||||
DomainObjectAdapter dobj = projectData.getOpenedDomainObject(getPathname());
|
||||
return dobj != null && dobj.isChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return fileManager.getOpenedDomainObject(getPathname()) != null;
|
||||
return projectData.getOpenedDomainObject(getPathname()) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -640,7 +640,7 @@ public class GhidraFile implements DomainFile {
|
|||
return false;
|
||||
}
|
||||
GhidraFile other = (GhidraFile) obj;
|
||||
if (fileManager != other.fileManager) {
|
||||
if (projectData != other.projectData) {
|
||||
return false;
|
||||
}
|
||||
return getPathname().equals(other.getPathname());
|
||||
|
@ -653,11 +653,11 @@ public class GhidraFile implements DomainFile {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = parent.getProjectData().getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
if (projectLocator.isTransient()) {
|
||||
return fileManager.getProjectLocator().getName() + getPathname();
|
||||
return projectLocator.getName() + getPathname();
|
||||
}
|
||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
||||
return projectLocator.getName() + ":" + getPathname();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,7 +20,6 @@ import java.net.MalformedURLException;
|
|||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
|
@ -32,7 +31,7 @@ import ghidra.util.task.TaskMonitor;
|
|||
|
||||
public class GhidraFolder implements DomainFolder {
|
||||
|
||||
private ProjectFileManager fileManager;
|
||||
private DefaultProjectData projectData;
|
||||
private LocalFileSystem fileSystem;
|
||||
private FileSystem versionedFileSystem;
|
||||
private DomainFolderChangeListener listener;
|
||||
|
@ -40,10 +39,10 @@ public class GhidraFolder implements DomainFolder {
|
|||
private GhidraFolder parent;
|
||||
private String name;
|
||||
|
||||
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
this.fileManager = fileManager;
|
||||
this.fileSystem = fileManager.getLocalFileSystem();
|
||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
||||
GhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
this.projectData = projectData;
|
||||
this.fileSystem = projectData.getLocalFileSystem();
|
||||
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||
this.listener = listener;
|
||||
this.name = FileSystem.SEPARATOR;
|
||||
}
|
||||
|
@ -52,7 +51,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||
this.listener = parent.getChangeListener();
|
||||
|
@ -67,17 +66,13 @@ public class GhidraFolder implements DomainFolder {
|
|||
}
|
||||
|
||||
LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
DomainFolderChangeListener getChangeListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
ProjectFileManager getProjectFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
|
||||
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
|
||||
if (fileData == null) {
|
||||
|
@ -88,7 +83,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
GhidraFolderData getFolderData() throws FileNotFoundException {
|
||||
if (parent == null) {
|
||||
return fileManager.getRootFolderData();
|
||||
return projectData.getRootFolderData();
|
||||
}
|
||||
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
|
||||
if (folderData == null) {
|
||||
|
@ -106,7 +101,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
private GhidraFolderData createFolderData(String folderName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
GhidraFolderData parentData =
|
||||
parent == null ? fileManager.getRootFolderData() : createFolderData();
|
||||
parent == null ? projectData.getRootFolderData() : createFolderData();
|
||||
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
|
||||
if (folderData == null) {
|
||||
try {
|
||||
|
@ -121,7 +116,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
}
|
||||
|
||||
private GhidraFolderData createFolderData() throws IOException {
|
||||
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
|
||||
GhidraFolderData rootFolderData = projectData.getRootFolderData();
|
||||
if (parent == null) {
|
||||
return rootFolderData;
|
||||
}
|
||||
|
@ -153,12 +148,12 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
@Override
|
||||
public ProjectLocator getProjectLocator() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectFileManager getProjectData() {
|
||||
return fileManager;
|
||||
public DefaultProjectData getProjectData() {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
String getPathname(String childName) {
|
||||
|
@ -185,18 +180,9 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
ProjectLocator projectLocator = getProjectLocator();
|
||||
URL projectURL = projectLocator.getURL();
|
||||
if (!GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||
RepositoryAdapter repository = fileManager.getRepository();
|
||||
if (repository == null) {
|
||||
return null;
|
||||
}
|
||||
// NOTE: only supports ghidra protocol without extension protocol.
|
||||
// Assumes any extension protocol use would be reflected in projectLocator URL.
|
||||
ServerInfo serverInfo = repository.getServerInfo();
|
||||
projectURL = GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
||||
repository.getName());
|
||||
URL projectURL = projectData.getSharedProjectURL();
|
||||
if (projectURL == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
|
@ -218,7 +204,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
ProjectLocator projectLocator = parent.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
if (!projectLocator.isTransient()) {
|
||||
return GhidraURL.makeURL(projectLocator, getPathname(), null);
|
||||
}
|
||||
|
@ -227,7 +213,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
@Override
|
||||
public boolean isInWritableProject() {
|
||||
return !getProjectData().getLocalFileSystem().isReadOnly();
|
||||
return !fileSystem.isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -319,7 +305,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
|
||||
Msg.error(this, "file error for " + getPathname(fileName), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -424,7 +410,7 @@ public class GhidraFolder implements DomainFolder {
|
|||
return false;
|
||||
}
|
||||
GhidraFolder other = (GhidraFolder) obj;
|
||||
if (fileManager != other.fileManager) {
|
||||
if (projectData != other.projectData) {
|
||||
return false;
|
||||
}
|
||||
return getPathname().equals(other.getPathname());
|
||||
|
@ -437,11 +423,11 @@ public class GhidraFolder implements DomainFolder {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
if (projectLocator.isTransient()) {
|
||||
return fileManager.getProjectLocator().getName() + getPathname();
|
||||
return projectData.getProjectLocator().getName() + getPathname();
|
||||
}
|
||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
||||
return projectData.getProjectLocator().getName() + ":" + getPathname();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,14 +24,25 @@ import ghidra.framework.model.*;
|
|||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.protocol.ghidra.TransientProjectData;
|
||||
import ghidra.framework.store.FileSystem;
|
||||
import ghidra.framework.store.FolderNotEmptyException;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link GhidraFolderData} provides the managed object which represents a project folder that
|
||||
* corresponds to matched folder paths across both a versioned and private
|
||||
* filesystem and viewed as a single folder at the project level. This class closely mirrors the
|
||||
* {@link DomainFolder} interface and is used by the {@link GhidraFolder} implementation; both of which
|
||||
* represent immutable folder references. Changes made to this folder's name or path are not reflected
|
||||
* in old {@link DomainFolder} instances and must be re-instantiated following such a change.
|
||||
* Any long-term retention of {@link DomainFolder} and {@link DomainFile} instances requires an
|
||||
* appropriate change listener to properly discard/reacquire such instances.
|
||||
*/
|
||||
class GhidraFolderData {
|
||||
|
||||
private ProjectFileManager fileManager;
|
||||
private DefaultProjectData projectData;
|
||||
|
||||
/**
|
||||
* Folder change listener - change events only sent if folder is visited
|
||||
|
@ -59,17 +70,24 @@ class GhidraFolderData {
|
|||
private boolean versionedFolderExists;
|
||||
|
||||
/**
|
||||
* General constructor reserved for root folder use only
|
||||
* @param fileManager
|
||||
* @param listener
|
||||
* General constructor reserved for root folder instantiation
|
||||
* @param projectData associated project data instance
|
||||
* @param listener folder change listener
|
||||
*/
|
||||
GhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
this.fileManager = fileManager;
|
||||
this.fileSystem = fileManager.getLocalFileSystem();
|
||||
this.versionedFileSystem = fileManager.getVersionedFileSystem();
|
||||
GhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
this.projectData = projectData;
|
||||
this.fileSystem = projectData.getLocalFileSystem();
|
||||
this.versionedFileSystem = projectData.getVersionedFileSystem();
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a folder instance with a specified name and a correpsonding parent folder
|
||||
* @param parent parent folder
|
||||
* @param name folder name
|
||||
* @throws FileNotFoundException if folder not found or error occured while checking
|
||||
* for its existance
|
||||
*/
|
||||
GhidraFolderData(GhidraFolderData parent, String name) throws FileNotFoundException {
|
||||
if (name == null || name.isEmpty()) {
|
||||
throw new FileNotFoundException("Bad folder name: blank or null");
|
||||
|
@ -77,7 +95,7 @@ class GhidraFolderData {
|
|||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.fileManager = parent.getProjectFileManager();
|
||||
this.projectData = parent.getProjectData();
|
||||
this.fileSystem = parent.getLocalFileSystem();
|
||||
this.versionedFileSystem = parent.getVersionedFileSystem();
|
||||
this.listener = parent.getChangeListener();
|
||||
|
@ -95,36 +113,59 @@ class GhidraFolderData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if folder has complete list of children
|
||||
* @return true if folder has complete list of children
|
||||
*/
|
||||
boolean visited() {
|
||||
return visited;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local file system
|
||||
*/
|
||||
LocalFileSystem getLocalFileSystem() {
|
||||
return fileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return versioned file system
|
||||
*/
|
||||
FileSystem getVersionedFileSystem() {
|
||||
return versionedFileSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return local user data file system
|
||||
*/
|
||||
LocalFileSystem getUserFileSystem() {
|
||||
return fileManager.getUserFileSystem();
|
||||
return projectData.getUserFileSystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return folder change listener
|
||||
*/
|
||||
DomainFolderChangeListener getChangeListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
ProjectFileManager getProjectFileManager() {
|
||||
return fileManager;
|
||||
/**
|
||||
* @return project data instance
|
||||
*/
|
||||
DefaultProjectData getProjectData() {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project locator which identifies the system storage
|
||||
* are for the local file system and other project related resources.
|
||||
* @return local project locator
|
||||
*/
|
||||
ProjectLocator getProjectLocator() {
|
||||
return fileManager.getProjectLocator();
|
||||
return projectData.getProjectLocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return this folder's parent folder or null if this is the root folder.
|
||||
*/
|
||||
GhidraFolderData getParentData() {
|
||||
return parent;
|
||||
}
|
||||
|
@ -143,7 +184,7 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
else if (folderPath.startsWith(FileSystem.SEPARATOR)) {
|
||||
return fileManager.getRootFolderData().getFolderPathData(folderPath, lazy);
|
||||
return projectData.getRootFolderData().getFolderPathData(folderPath, lazy);
|
||||
}
|
||||
if (folderPath.length() == 0) {
|
||||
return this;
|
||||
|
@ -168,10 +209,26 @@ class GhidraFolderData {
|
|||
return folderData.getFolderPathData(nextPath, lazy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this folder's name.
|
||||
* @return the name
|
||||
*/
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name on this domain folder.
|
||||
* @param newName domain folder name
|
||||
* @return renamed domain file (the original DomainFolder object becomes invalid since it is
|
||||
* immutable)
|
||||
* @throws InvalidNameException if newName contains illegal characters
|
||||
* @throws DuplicateFileException if a folder named newName
|
||||
* already exists in this files domain folder.
|
||||
* @throws FileInUseException if any file within this folder or its descendants is
|
||||
* in-use / checked-out.
|
||||
* @throws IOException thrown if an IO or access error occurs.
|
||||
*/
|
||||
GhidraFolder setName(String newName) throws InvalidNameException, IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (parent == null || fileSystem.isReadOnly()) {
|
||||
|
@ -242,6 +299,10 @@ class GhidraFolderData {
|
|||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full path name to this folder
|
||||
* @return the path name
|
||||
*/
|
||||
String getPathname() {
|
||||
if (parent == null) {
|
||||
return FileSystem.SEPARATOR;
|
||||
|
@ -254,6 +315,10 @@ class GhidraFolderData {
|
|||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this folder contains any sub-folders or domain files.
|
||||
* @return true if this folder is empty.
|
||||
*/
|
||||
boolean isEmpty() {
|
||||
try {
|
||||
refresh(false, false, null); // visited will be true upon return
|
||||
|
@ -266,6 +331,10 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of names for all files contained within this folder.
|
||||
* @return list of file names
|
||||
*/
|
||||
List<String> getFileNames() {
|
||||
try {
|
||||
refresh(false, false, null); // visited will be true upon return
|
||||
|
@ -277,6 +346,10 @@ class GhidraFolderData {
|
|||
return new ArrayList<>(fileList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of names for all subfolders contained within this folder.
|
||||
* @return list of file names
|
||||
*/
|
||||
List<String> getFolderNames() {
|
||||
try {
|
||||
refresh(false, false, null); // visited will be true upon return
|
||||
|
@ -289,9 +362,10 @@ class GhidraFolderData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Update file list/cache based upon rename of file.
|
||||
* If this folder has been visited listener will be notified with rename
|
||||
* @param oldName
|
||||
* Update file list/cache based upon rename of a file.
|
||||
* If this folder has been visited the listener will be notified with rename
|
||||
* @param oldFileName file name prior to rename
|
||||
* @param newFileName file name after rename
|
||||
*/
|
||||
void fileRenamed(String oldFileName, String newFileName) {
|
||||
GhidraFileData fileData;
|
||||
|
@ -314,6 +388,14 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file list/cache based upon change of parent for a file.
|
||||
* If this folder or the newParent has been visited the listener will be notified with add/move
|
||||
* details.
|
||||
* @param newParent new parent folder
|
||||
* @param oldFileName file name prior to move
|
||||
* @param newFileName file name after move
|
||||
*/
|
||||
void fileMoved(GhidraFolderData newParent, String oldFileName, String newFileName) {
|
||||
GhidraFileData fileData;
|
||||
synchronized (fileSystem) {
|
||||
|
@ -340,7 +422,7 @@ class GhidraFolderData {
|
|||
* underlying local or versioned file. If this folder has been visited an appropriate
|
||||
* add/remove/change notification will be provided to the listener.
|
||||
* NOTE: Move and Rename situations are not handled
|
||||
* @param fileName
|
||||
* @param fileName name of file which has changed
|
||||
*/
|
||||
void fileChanged(String fileName) {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -389,7 +471,8 @@ class GhidraFolderData {
|
|||
* visited an appropriate add/remove/change notification will be provided to the listener.
|
||||
* NOTE: Care should be taken using this method as all sub-folder cache data may be disposed!
|
||||
* NOTE: Move and Rename situations are not handled
|
||||
* @param folderName
|
||||
* @param folderName name of folder which has changed
|
||||
* @throws IOException if an IO error occurs during associated refresh
|
||||
*/
|
||||
void folderChanged(String folderName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -404,7 +487,7 @@ class GhidraFolderData {
|
|||
if (folderData.versionedFolderExists || folderData.folderExists) {
|
||||
// preserve subfolder data
|
||||
if (folderData.visited) {
|
||||
folderData.refresh(true, true, fileManager.getProjectDisposalMonitor());
|
||||
folderData.refresh(true, true, projectData.getProjectDisposalMonitor());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -429,7 +512,7 @@ class GhidraFolderData {
|
|||
/**
|
||||
* Remove and dispose specified subfolder data and notify listener of removal
|
||||
* if this folder has been visited
|
||||
* @param folderName
|
||||
* @param folderName name of folder which was removed
|
||||
*/
|
||||
void folderRemoved(String folderName) {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -443,6 +526,9 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the cached data for this folder and all of its children recursively.
|
||||
*/
|
||||
void dispose() {
|
||||
visited = false;
|
||||
folderList.clear();
|
||||
|
@ -458,7 +544,7 @@ class GhidraFolderData {
|
|||
// NOTE: clearing the following can cause issues since there may be some residual
|
||||
// activity/use which will get a NPE
|
||||
// parent = null;
|
||||
// fileManager = null;
|
||||
// projectData = null;
|
||||
// listener = null;
|
||||
}
|
||||
|
||||
|
@ -476,7 +562,7 @@ class GhidraFolderData {
|
|||
* Refresh set of sub-folder names and identify added/removed folders.
|
||||
* @param recursive recurse into visited subfolders if true
|
||||
* @param monitor recursion task monitor - break from recursion if cancelled
|
||||
* @throws IOException
|
||||
* @throws IOException if an IO error occurs during the refresh
|
||||
*/
|
||||
private void refreshFolders(boolean recursive, TaskMonitor monitor) throws IOException {
|
||||
|
||||
|
@ -653,7 +739,7 @@ class GhidraFolderData {
|
|||
* of visited state, if false refresh is lazy and will not be
|
||||
* performed if a previous refresh set the visited state.
|
||||
* @param monitor recursion task monitor - break from recursion if cancelled
|
||||
* @throws IOException
|
||||
* @throws IOException if an IO error occurs during the refresh
|
||||
*/
|
||||
void refresh(boolean recursive, boolean force, TaskMonitor monitor) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -699,11 +785,11 @@ class GhidraFolderData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check for existence of subfolder. If this folder visited, rely on folderList
|
||||
* @param fileName
|
||||
* @param doRealCheck if true do not rely on fileList
|
||||
* @return
|
||||
* @throws IOException
|
||||
* Check for existence of subfolder. If this folder has previously been visited,
|
||||
* rely on the cached folderList.
|
||||
* @param folderName name of folder to look for
|
||||
* @return true if folder exists, else false
|
||||
* @throws IOException if an IO error occurs when checking for folder's existance.
|
||||
*/
|
||||
boolean containsFolder(String folderName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -720,8 +806,8 @@ class GhidraFolderData {
|
|||
/**
|
||||
* Create and add new subfolder data object to cache. Data will not be created
|
||||
* if folder does not exist or an IOException occurs.
|
||||
* @param folderName
|
||||
* @return folder data or null
|
||||
* @param folderName name of folder to be added
|
||||
* @return folder data or null if folder does not exist
|
||||
*/
|
||||
private GhidraFolderData addFolderData(String folderName) {
|
||||
GhidraFolderData folderData = folderDataCache.get(folderName);
|
||||
|
@ -739,7 +825,7 @@ class GhidraFolderData {
|
|||
|
||||
/**
|
||||
* Get folder data for child folder specified by folderName
|
||||
* @param folderName
|
||||
* @param folderName name of folder
|
||||
* @param lazy if true folder will not be searched for if not already discovered - in
|
||||
* this case null will be returned
|
||||
* @return folder data or null if not found or lazy=true and not yet discovered
|
||||
|
@ -763,10 +849,10 @@ class GhidraFolderData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check for existence of file. If folder visited, rely on fileDataCache
|
||||
* @param fileName the name of the file to check for
|
||||
* @return true if this folder contains the fileName
|
||||
* @throws IOException
|
||||
* Check for existence of file. If folder previously visited, rely on fileDataCache
|
||||
* @param fileName the name of the file to look for
|
||||
* @return true if this folder contains the fileName, else false
|
||||
* @throws IOException if an IO error occurs while checking for file existance
|
||||
*/
|
||||
public boolean containsFile(String fileName) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -783,9 +869,9 @@ class GhidraFolderData {
|
|||
/**
|
||||
* Create and add new file data object to cache. Data will not be created
|
||||
* if file does not exist or an IOException occurs.
|
||||
* @param fileName
|
||||
* @return file data or null
|
||||
* @throws IOException
|
||||
* @param fileName name of file
|
||||
* @return file data or null if not found
|
||||
* @throws IOException if an IO error occurs while checking for file existance
|
||||
*/
|
||||
private GhidraFileData addFileData(String fileName) throws IOException {
|
||||
GhidraFileData fileData = fileDataCache.get(fileName);
|
||||
|
@ -793,7 +879,7 @@ class GhidraFolderData {
|
|||
try {
|
||||
fileData = new GhidraFileData(this, fileName);
|
||||
fileDataCache.put(fileName, fileData);
|
||||
fileManager.updateFileIndex(fileData);
|
||||
projectData.updateFileIndex(fileData);
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// ignore
|
||||
|
@ -804,10 +890,11 @@ class GhidraFolderData {
|
|||
|
||||
/**
|
||||
* Get file data for child specified by fileName
|
||||
* @param fileName
|
||||
* @param fileName name of file
|
||||
* @param lazy if true file will not be searched for if not already discovered - in
|
||||
* this case null will be returned
|
||||
* @return file data or null if not found or lazy=true and not yet discovered
|
||||
* @throws IOException if an IO error occurs while checking for file existance
|
||||
*/
|
||||
GhidraFileData getFileData(String fileName, boolean lazy) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -822,58 +909,11 @@ class GhidraFolderData {
|
|||
return null;
|
||||
}
|
||||
|
||||
// // TODO: Examine!
|
||||
// private void removeFolderX(String folderName) {
|
||||
// folderList.remove(folderName);
|
||||
// folderDataCache.remove(folderName);
|
||||
// listener.domainFolderRemoved(getDomainFolder(), folderName);
|
||||
// }
|
||||
//
|
||||
// // TODO: Examine!
|
||||
// void removeFileX(String fileName) {
|
||||
// fileList.remove(fileName);
|
||||
// GhidraFileV2Data fileData = fileDataCache.remove(fileName);
|
||||
// if (fileData != null) {
|
||||
// fileData.dispose();
|
||||
// }
|
||||
//// TODO: May need to eliminate presence of fileID in callback
|
||||
// listener.domainFileRemoved(getDomainFolder(), fileName, null /* fileID */);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Handle addition of new file. If this folder has been visited, listener
|
||||
// * will be notified of new file addition or change
|
||||
// * @param fileName
|
||||
// * @return
|
||||
// */
|
||||
// // TODO: Examine!
|
||||
// GhidraFile fileAddedX(String fileName) {
|
||||
// invalidateFile(fileName);
|
||||
// GhidraFile df = getDomainFile(fileName);
|
||||
// if (visited) {
|
||||
// getFileData(fileName, false);
|
||||
// if (fileList.add(fileName)) {
|
||||
// listener.domainFileAdded(df);
|
||||
// }
|
||||
// else {
|
||||
// listenerX.domainFileStatusChanged(df, fileID)
|
||||
// }
|
||||
// }
|
||||
// return df;
|
||||
// }
|
||||
//
|
||||
|
||||
//
|
||||
// // TODO: Examine!
|
||||
// private GhidraFolder addFolderX(String folderName) {
|
||||
// invalidateFolder(folderName, false);
|
||||
// GhidraFolder folder = getDomainFolder(folderName);
|
||||
// if (folderList.add(folderName) && visited) {
|
||||
// listener.domainFolderAdded(folder);
|
||||
// }
|
||||
// return folder;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get the domain file in this folder with the given fileName.
|
||||
* @param fileName name of file in this folder to retrieve
|
||||
* @return domain file or null if there is no file in this folder with the given name.
|
||||
*/
|
||||
GhidraFile getDomainFile(String fileName) {
|
||||
synchronized (fileSystem) {
|
||||
try {
|
||||
|
@ -888,6 +928,11 @@ class GhidraFolderData {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the domain folder in this folder with the given subfolderName.
|
||||
* @param subfolderName name of subfolder in this folder to retrieve
|
||||
* @return domain folder or null if there is no file in this folder with the given name.
|
||||
*/
|
||||
GhidraFolder getDomainFolder(String subfolderName) {
|
||||
synchronized (fileSystem) {
|
||||
try {
|
||||
|
@ -902,10 +947,26 @@ class GhidraFolderData {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link DomainFolder} instance which corresponds to this folder
|
||||
*/
|
||||
GhidraFolder getDomainFolder() {
|
||||
return new GhidraFolder(parent.getDomainFolder(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a domain object to this folder.
|
||||
* @param fileName domain file name
|
||||
* @param obj domain object to be stored
|
||||
* @param monitor progress monitor
|
||||
* @return domain file created as a result of adding
|
||||
* the domain object to this folder
|
||||
* @throws DuplicateFileException thrown if the file name already exists
|
||||
* @throws InvalidNameException if name is an empty string
|
||||
* or if it contains characters other than alphanumerics.
|
||||
* @throws IOException if IO or access error occurs
|
||||
* @throws CancelledException if the user cancels the create.
|
||||
*/
|
||||
GhidraFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
|
||||
throws InvalidNameException, IOException, CancelledException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -937,9 +998,12 @@ class GhidraFolderData {
|
|||
throw new IOException("File creation failed for unknown reason");
|
||||
}
|
||||
|
||||
fileManager.setDomainObject(file.getPathname(), doa);
|
||||
projectData.setDomainObject(file.getPathname(), doa);
|
||||
doa.setDomainFile(file);
|
||||
doa.setChanged(false);
|
||||
|
||||
projectData.trackDomainFileInUse(doa);
|
||||
|
||||
listener.domainFileObjectOpenedForUpdate(file, doa);
|
||||
|
||||
return file;
|
||||
|
@ -950,6 +1014,19 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new domain file to this folder.
|
||||
* @param fileName domain file name
|
||||
* @param packFile packed file containing domain file data
|
||||
* @param monitor progress monitor
|
||||
* @return domain file created as a result of adding
|
||||
* the domain object to this folder
|
||||
* @throws DuplicateFileException thrown if the file name already exists
|
||||
* @throws InvalidNameException if name is an empty string
|
||||
* or if it contains characters other than alphanumerics.
|
||||
* @throws IOException if IO or access error occurs
|
||||
* @throws CancelledException if the user cancels the create.
|
||||
*/
|
||||
GhidraFile createFile(String fileName, File packFile, TaskMonitor monitor)
|
||||
throws InvalidNameException, IOException, CancelledException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -969,6 +1046,15 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subfolder within this folder.
|
||||
* @param folderName sub-folder name
|
||||
* @return the new folder
|
||||
* @throws DuplicateFileException if a folder by this name already exists
|
||||
* @throws InvalidNameException if name is an empty string of if it contains characters other
|
||||
* than alphanumerics.
|
||||
* @throws IOException if IO or access error occurs
|
||||
*/
|
||||
GhidraFolderData createFolder(String folderName) throws InvalidNameException, IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (fileSystem.isReadOnly()) {
|
||||
|
@ -984,6 +1070,11 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this folder, if empty, from the local filesystem
|
||||
* @throws IOException if IO or access error occurs
|
||||
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
|
||||
*/
|
||||
void delete() throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (fileSystem.isReadOnly()) {
|
||||
|
@ -1000,6 +1091,9 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this folder from the local filesystem if empty
|
||||
*/
|
||||
void deleteLocalFolderIfEmpty() {
|
||||
synchronized (fileSystem) {
|
||||
try {
|
||||
|
@ -1018,6 +1112,19 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this folder into the newParent folder. If connected to a repository
|
||||
* this moves both private and repository folders/files. If not
|
||||
* connected, only private folders/files are moved.
|
||||
* @param newParent new parent folder within the same project
|
||||
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
|
||||
* it is immutable)
|
||||
* @throws DuplicateFileException if a folder with the same name
|
||||
* already exists in newParent folder.
|
||||
* @throws FileInUseException if this folder or one of its descendants
|
||||
* contains a file which is in-use / checked-out.
|
||||
* @throws IOException thrown if an IO or access error occurs.
|
||||
*/
|
||||
GhidraFolder moveTo(GhidraFolderData newParent) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
if (newParent.getLocalFileSystem() != fileSystem || fileSystem.isReadOnly()) {
|
||||
|
@ -1084,11 +1191,17 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the specified folder if an ancestor of this folder
|
||||
* (i.e., parent, grand-parent, etc.).
|
||||
* @param folderData folder to be checked
|
||||
* @return true if the specified folder if an ancestor of this folder
|
||||
*/
|
||||
boolean isAncestor(GhidraFolderData folderData) {
|
||||
if (!folderData.fileManager.getProjectLocator().equals(fileManager.getProjectLocator())) {
|
||||
if (!folderData.projectData.getProjectLocator().equals(projectData.getProjectLocator())) {
|
||||
// check if projects share a common repository
|
||||
RepositoryAdapter myRepository = fileManager.getRepository();
|
||||
RepositoryAdapter otherRepository = folderData.fileManager.getRepository();
|
||||
RepositoryAdapter myRepository = projectData.getRepository();
|
||||
RepositoryAdapter otherRepository = folderData.projectData.getRepository();
|
||||
if (myRepository == null || otherRepository == null ||
|
||||
!myRepository.getServerInfo().equals(otherRepository.getServerInfo()) ||
|
||||
!myRepository.getName().equals(otherRepository.getName())) {
|
||||
|
@ -1105,20 +1218,30 @@ class GhidraFolderData {
|
|||
return false;
|
||||
}
|
||||
|
||||
GhidraFolder copyTo(GhidraFolderData newParentData, TaskMonitor monitor)
|
||||
/**
|
||||
* Copy this folder into the newParent folder.
|
||||
* @param newParent new parent folder
|
||||
* @param monitor the task monitor
|
||||
* @return the new copied folder
|
||||
* @throws DuplicateFileException if a folder or file by
|
||||
* this name already exists in the newParent folder
|
||||
* @throws IOException thrown if an IO or access error occurs.
|
||||
* @throws CancelledException if task monitor cancelled operation.
|
||||
*/
|
||||
GhidraFolder copyTo(GhidraFolderData newParent, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
synchronized (fileSystem) {
|
||||
if (newParentData.fileSystem.isReadOnly()) {
|
||||
if (newParent.fileSystem.isReadOnly()) {
|
||||
throw new ReadOnlyException("copyTo permitted to writeable project only");
|
||||
}
|
||||
if (isAncestor(newParentData)) {
|
||||
if (isAncestor(newParent)) {
|
||||
throw new IOException("self-referencing copy not permitted");
|
||||
}
|
||||
GhidraFolderData newFolderData = newParentData.getFolderData(name, false);
|
||||
GhidraFolderData newFolderData = newParent.getFolderData(name, false);
|
||||
|
||||
if (newFolderData == null) {
|
||||
try {
|
||||
newFolderData = newParentData.createFolder(name);
|
||||
newFolderData = newParent.createFolder(name);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new AssertException("Unexpected error", e);
|
||||
|
@ -1144,22 +1267,46 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
DomainFile copyToAsLink(GhidraFolderData newParentData) throws IOException {
|
||||
/**
|
||||
* Create a new link-file in the specified newParent which will reference this folder
|
||||
* (i.e., linked-folder). Restrictions:
|
||||
* <ul>
|
||||
* <li>Specified newParent must reside within a different project since internal linking is
|
||||
* not currently supported.</li>
|
||||
* </ul>
|
||||
* If this folder is associated with a temporary transient project (i.e., not a locally
|
||||
* managed project) the generated link will refer to the remote folder with a remote
|
||||
* Ghidra URL, otherwise a local project storage path will be used.
|
||||
* @param newParent new parent folder where link-file is to be created
|
||||
* @return newly created domain file (i.e., link-file) or null if link use not supported.
|
||||
* @throws IOException if an IO or access error occurs.
|
||||
*/
|
||||
DomainFile copyToAsLink(GhidraFolderData newParent) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
String linkFilename = name;
|
||||
if (linkFilename == null) {
|
||||
if (fileManager instanceof TransientProjectData) {
|
||||
linkFilename = fileManager.getRepository().getName();
|
||||
if (projectData instanceof TransientProjectData) {
|
||||
linkFilename = projectData.getRepository().getName();
|
||||
}
|
||||
else {
|
||||
linkFilename = fileManager.getProjectLocator().getName();
|
||||
linkFilename = projectData.getProjectLocator().getName();
|
||||
}
|
||||
}
|
||||
return newParentData.copyAsLink(fileManager, getPathname(), linkFilename,
|
||||
return newParent.copyAsLink(projectData, getPathname(), linkFilename,
|
||||
FolderLinkContentHandler.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a link-file within this folder. The link-file may correspond to various types of
|
||||
* content (e.g., Program, Trace, Folder, etc.) based upon specified link handler.
|
||||
* @param sourceProjectData referenced content project data within which specified path exists.
|
||||
* @param pathname path of referenced content with source project data
|
||||
* @param linkFilename name of link-file to be created within this folder.
|
||||
* @param lh link file handler used to create specific link file.
|
||||
* @return link-file
|
||||
* @throws IOException if IO error occurs during link creation
|
||||
*/
|
||||
DomainFile copyAsLink(ProjectData sourceProjectData, String pathname, String linkFilename,
|
||||
LinkHandler<?> lh) throws IOException {
|
||||
synchronized (fileSystem) {
|
||||
|
@ -1167,7 +1314,7 @@ class GhidraFolderData {
|
|||
throw new ReadOnlyException("copyAsLink permitted to writeable project only");
|
||||
}
|
||||
|
||||
if (sourceProjectData == fileManager) {
|
||||
if (sourceProjectData == projectData) {
|
||||
// internal linking not yet supported
|
||||
Msg.error(this, "Internal file/folder links not yet supported");
|
||||
return null;
|
||||
|
@ -1177,13 +1324,12 @@ class GhidraFolderData {
|
|||
if (sourceProjectData instanceof TransientProjectData) {
|
||||
RepositoryAdapter repository = sourceProjectData.getRepository();
|
||||
ServerInfo serverInfo = repository.getServerInfo();
|
||||
ghidraUrl =
|
||||
GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
|
||||
repository.getName(), pathname);
|
||||
ghidraUrl = GhidraURL.makeURL(serverInfo.getServerName(),
|
||||
serverInfo.getPortNumber(), repository.getName(), pathname);
|
||||
}
|
||||
else {
|
||||
ProjectLocator projectLocator = sourceProjectData.getProjectLocator();
|
||||
if (projectLocator.equals(fileManager.getProjectLocator())) {
|
||||
if (projectLocator.equals(projectData.getProjectLocator())) {
|
||||
return null; // local internal linking not supported
|
||||
}
|
||||
ghidraUrl = GhidraURL.makeURL(projectLocator, pathname, null);
|
||||
|
@ -1216,6 +1362,14 @@ class GhidraFolderData {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a non-conflicting file name for this folder based upon the specified preferred name.
|
||||
* NOTE: This method is subject to race conditions where returned name could conflict by the
|
||||
* time it is actually used.
|
||||
* @param preferredName preferred file name
|
||||
* @return non-conflicting file name
|
||||
* @throws IOException if an IO error occurs during file checks
|
||||
*/
|
||||
String getTargetName(String preferredName) throws IOException {
|
||||
String newName = preferredName;
|
||||
int i = 1;
|
||||
|
@ -1242,11 +1396,11 @@ class GhidraFolderData {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
ProjectLocator projectLocator = fileManager.getProjectLocator();
|
||||
ProjectLocator projectLocator = projectData.getProjectLocator();
|
||||
if (projectLocator.isTransient()) {
|
||||
return fileManager.getProjectLocator().getName() + getPathname();
|
||||
return projectData.getProjectLocator().getName() + getPathname();
|
||||
}
|
||||
return fileManager.getProjectLocator().getName() + ":" + getPathname();
|
||||
return projectData.getProjectLocator().getName() + ":" + getPathname();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,8 +19,8 @@ import ghidra.framework.model.DomainFolderChangeListener;
|
|||
|
||||
public class RootGhidraFolder extends GhidraFolder {
|
||||
|
||||
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
super(fileManager, listener);
|
||||
RootGhidraFolder(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
super(projectData, listener);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,18 +20,18 @@ import ghidra.framework.store.FileSystem;
|
|||
|
||||
public class RootGhidraFolderData extends GhidraFolderData {
|
||||
|
||||
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
|
||||
super(fileManager, listener);
|
||||
RootGhidraFolderData(DefaultProjectData projectData, DomainFolderChangeListener listener) {
|
||||
super(projectData, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
GhidraFolder getDomainFolder() {
|
||||
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
|
||||
return new RootGhidraFolder(getProjectData(), getChangeListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided for testing use only
|
||||
* @param fs
|
||||
* @param fs versioned file system
|
||||
*/
|
||||
void setVersionedFileSystem(FileSystem fs) {
|
||||
versionedFileSystem = fs;
|
||||
|
|
|
@ -53,7 +53,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
public final static String READ_ONLY_PROPERTY = "READ_ONLY";
|
||||
|
||||
/**
|
||||
* Get the name of the StoredObj that is associated with the data.
|
||||
* Get the name of this project file
|
||||
* @return the name
|
||||
*/
|
||||
public String getName();
|
||||
|
@ -83,7 +83,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
public DomainFile setName(String newName) throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Returns the path name to the domain object.
|
||||
* Returns the full path name to this file
|
||||
* @return the path name
|
||||
*/
|
||||
public String getPathname();
|
||||
|
@ -114,7 +114,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
public ProjectLocator getProjectLocator();
|
||||
|
||||
/**
|
||||
* Returns content-type string
|
||||
* Returns content-type string for this file
|
||||
* @return the file content type or a reserved content type {@link ContentHandler#MISSING_CONTENT}
|
||||
* or {@link ContentHandler#UNKNOWN_CONTENT}.
|
||||
*/
|
||||
|
@ -134,8 +134,11 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
|
||||
/**
|
||||
* Returns changes made to versioned file by others since checkout was performed.
|
||||
* NOTE: This method is unable to cope with version issues which may require an
|
||||
* upgrade.
|
||||
* @return change set or null
|
||||
* @throws VersionException latest version was created with a newer version of software
|
||||
* @throws VersionException latest version was created with a different version of software
|
||||
* which prevents rapid determination of change set.
|
||||
* @throws IOException if a folder item access error occurs or change set was
|
||||
* produced by newer version of software and can not be read
|
||||
*/
|
||||
|
@ -426,7 +429,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
* @param keep if true, the private database will be renamed with a .keep
|
||||
* extension.
|
||||
* @throws NotConnectedException if shared project and not connected to repository
|
||||
* @throws FileInUseException if this file is in-use / checked-out.
|
||||
* @throws FileInUseException if this file is in-use.
|
||||
* @throws IOException if file is not checked-out or an IO / access error occurs.
|
||||
*/
|
||||
public void undoCheckout(boolean keep) throws IOException;
|
||||
|
@ -549,7 +552,8 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
|
||||
/**
|
||||
* Get the list of consumers (Objects) for this domain file.
|
||||
* @return empty array list if there are no consumers
|
||||
* @return true if linking is supported allowing a link-file to be created which
|
||||
* references this file, else false.
|
||||
*/
|
||||
public List<?> getConsumers();
|
||||
|
||||
|
@ -593,7 +597,7 @@ public interface DomainFile extends Comparable<DomainFile> {
|
|||
/**
|
||||
* Returns the length of this domain file. This size is the minimum disk space
|
||||
* used for storing this file, but does not account for additional storage space
|
||||
* used to tracks changes, etc.
|
||||
* used to track changes, etc.
|
||||
* @return file length
|
||||
* @throws IOException if IO or access error occurs
|
||||
*/
|
||||
|
|
|
@ -83,7 +83,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
|||
public ProjectData getProjectData();
|
||||
|
||||
/**
|
||||
* Returns the path name to the domain object.
|
||||
* Returns the full path name to this folder
|
||||
* @return the path name
|
||||
*/
|
||||
public String getPathname();
|
||||
|
@ -183,11 +183,10 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
|||
throws InvalidNameException, IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Create a subfolder of this folder.
|
||||
* Create a subfolder within this folder.
|
||||
* @param folderName sub-folder name
|
||||
* @return the folder
|
||||
* @throws DuplicateFileException if a folder by
|
||||
* this name already exists
|
||||
* @return the new folder
|
||||
* @throws DuplicateFileException if a folder by this name already exists
|
||||
* @throws InvalidNameException if name is an empty string of if it contains characters other
|
||||
* than alphanumerics.
|
||||
* @throws IOException if IO or access error occurs
|
||||
|
@ -195,16 +194,16 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
|||
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
|
||||
|
||||
/**
|
||||
* Deletes this folder and all of its contents
|
||||
* Deletes this folder, if empty, from the local filesystem
|
||||
* @throws IOException if IO or access error occurs
|
||||
* @throws FolderNotEmptyException Thrown if the subfolder is not empty.
|
||||
* @throws FolderNotEmptyException Thrown if this folder is not empty.
|
||||
*/
|
||||
public void delete() throws IOException;
|
||||
|
||||
/**
|
||||
* Move this folder into the newParent folder. If connected to an archive
|
||||
* this affects both private and repository folders and files. If not
|
||||
* connected, only private folders and files are affected.
|
||||
* Move this folder into the newParent folder. If connected to a repository
|
||||
* this moves both private and repository folders/files. If not
|
||||
* connected, only private folders/files are moved.
|
||||
* @param newParent new parent folder within the same project
|
||||
* @return the newly relocated folder (the original DomainFolder object becomes invalid since
|
||||
* it is immutable)
|
||||
|
@ -220,7 +219,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
|||
* Copy this folder into the newParent folder.
|
||||
* @param newParent new parent folder
|
||||
* @param monitor the task monitor
|
||||
* @return the copied folder
|
||||
* @return the new copied folder
|
||||
* @throws DuplicateFileException if a folder or file by
|
||||
* this name already exists in the newParent folder
|
||||
* @throws IOException thrown if an IO or access error occurs.
|
||||
|
@ -230,16 +229,17 @@ public interface DomainFolder extends Comparable<DomainFolder> {
|
|||
throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Copy this folder into the newParent folder as a link file. Restrictions:
|
||||
* Create a new link-file in the specified newParent which will reference this folder
|
||||
* (i.e., linked-folder). Restrictions:
|
||||
* <ul>
|
||||
* <li>Specified newParent must reside within a different project since internal linking is
|
||||
* not currently supported.</li>
|
||||
* </ul>
|
||||
* If this folder is associated with a temporary transient project (i.e., not a locally
|
||||
* managed project) the generated link will refer to the remote file with a remote
|
||||
* managed project) the generated link will refer to the remote folder with a remote
|
||||
* Ghidra URL, otherwise a local project storage path will be used.
|
||||
* @param newParent new parent folder
|
||||
* @return newly created domain file or null if link use not supported.
|
||||
* @param newParent new parent folder where link-file is to be created
|
||||
* @return newly created domain file (i.e., link-file) or null if link use not supported.
|
||||
* @throws IOException if an IO or access error occurs.
|
||||
*/
|
||||
public DomainFile copyToAsLink(DomainFolder newParent) throws IOException;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -20,5 +19,10 @@ package ghidra.framework.model;
|
|||
* An interface that allows for a callback when a {@link DomainObject} is closed.
|
||||
*/
|
||||
public interface DomainObjectClosedListener {
|
||||
public void domainObjectClosed();
|
||||
|
||||
/**
|
||||
* Callback indicating that the specified {@link DomainObject} has been closed.
|
||||
* @param dobj domain object
|
||||
*/
|
||||
public void domainObjectClosed(DomainObject dobj);
|
||||
}
|
||||
|
|
|
@ -191,8 +191,10 @@ public interface ProjectData {
|
|||
TaskMonitor monitor) throws IOException, CancelledException;
|
||||
|
||||
/**
|
||||
* Close the project storage associated with this project data object.
|
||||
* NOTE: This should not be invoked if this object is utilized by a Project instance.
|
||||
* Initiate disposal of this project data object. Any files already open will delay
|
||||
* disposal until they are closed.
|
||||
* NOTE: This should only be invoked by the controlling object which created/opened this
|
||||
* instance to avoid premature disposal.
|
||||
*/
|
||||
public void close();
|
||||
|
||||
|
@ -209,4 +211,18 @@ public interface ProjectData {
|
|||
*/
|
||||
public void testValidName(String name, boolean isPath) throws InvalidNameException;
|
||||
|
||||
/**
|
||||
* Generate a repository URL which corresponds to this project data if applicable.
|
||||
* Local private projects will return null;
|
||||
* @return repository URL which corresponds to this project data or null if not applicable.
|
||||
*/
|
||||
public URL getSharedProjectURL();
|
||||
|
||||
/**
|
||||
* Generate a local URL which corresponds to this project data if applicable.
|
||||
* Remote transient project data will return null;
|
||||
* @return local URL which corresponds to this project data or null if not applicable.
|
||||
*/
|
||||
public URL getLocalProjectURL();
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.jdom.input.SAXBuilder;
|
|||
import org.jdom.output.XMLOutputter;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.data.TransientDataManager;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
@ -58,7 +58,7 @@ public class DefaultProject implements Project {
|
|||
private DefaultProjectManager projectManager;
|
||||
|
||||
private ProjectLocator projectLocator;
|
||||
private ProjectFileManager fileMgr;
|
||||
private DefaultProjectData projectData;
|
||||
private ToolManagerImpl toolManager;
|
||||
|
||||
private boolean changed; // flag for whether the project configuration has changed
|
||||
|
@ -66,7 +66,7 @@ public class DefaultProject implements Project {
|
|||
|
||||
private Map<String, SaveState> dataMap = new HashMap<>();
|
||||
private Map<String, ToolTemplate> projectConfigMap = new HashMap<>();
|
||||
private Map<URL, ProjectFileManager> otherViews = new HashMap<>();
|
||||
private Map<URL, DefaultProjectData> otherViewsMap = new HashMap<>();
|
||||
private Set<URL> visibleViews = new HashSet<>();
|
||||
private WeakSet<ProjectViewListener> viewListeners =
|
||||
WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
|
@ -86,21 +86,10 @@ public class DefaultProject implements Project {
|
|||
this.projectManager = projectManager;
|
||||
this.projectLocator = projectLocator;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Creating project: " + projectLocator.toString());
|
||||
fileMgr = new ProjectFileManager(projectLocator, repository, true);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Creating project: " + projectLocator.toString());
|
||||
projectData = new DefaultProjectData(projectLocator, repository, true);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
initializeNewProject();
|
||||
}
|
||||
|
@ -122,28 +111,18 @@ public class DefaultProject implements Project {
|
|||
this.projectManager = projectManager;
|
||||
this.projectLocator = projectLocator;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Opening project: " + projectLocator.toString());
|
||||
fileMgr = new ProjectFileManager(projectLocator, true, resetOwner);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
Msg.info(this, "Opening project: " + projectLocator.toString());
|
||||
projectData = new DefaultProjectData(projectLocator, true, resetOwner);
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for opening a URL-based project
|
||||
*
|
||||
* @param connection project connection
|
||||
* @param projectManager the manager of this project
|
||||
* @param connection project URL connection (not previously used)
|
||||
* @throws IOException if I/O error occurs.
|
||||
*/
|
||||
protected DefaultProject(DefaultProjectManager projectManager, GhidraURLConnection connection)
|
||||
|
@ -151,27 +130,17 @@ public class DefaultProject implements Project {
|
|||
|
||||
this.projectManager = projectManager;
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
||||
fileMgr = (ProjectFileManager) connection.getProjectData();
|
||||
if (fileMgr == null) {
|
||||
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
||||
}
|
||||
Msg.info(this, "Opening project/repository: " + connection.getURL());
|
||||
projectData = (DefaultProjectData) connection.getProjectData();
|
||||
if (projectData == null) {
|
||||
throw new IOException("Failed to open project/repository: " + connection.getURL());
|
||||
}
|
||||
|
||||
projectLocator = fileMgr.getProjectLocator();
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
finally {
|
||||
if (!success) {
|
||||
if (fileMgr != null) {
|
||||
fileMgr.dispose();
|
||||
}
|
||||
}
|
||||
projectLocator = projectData.getProjectLocator();
|
||||
if (!SystemUtilities.isInHeadlessMode()) {
|
||||
toolManager = new ToolManagerImpl(this);
|
||||
}
|
||||
|
||||
initializeNewProject();
|
||||
}
|
||||
|
||||
|
@ -300,20 +269,20 @@ public class DefaultProject implements Project {
|
|||
return null;
|
||||
}
|
||||
|
||||
ProjectFileManager projectData = (ProjectFileManager) c.getProjectData();
|
||||
DefaultProjectData projectData = (DefaultProjectData) c.getProjectData();
|
||||
if (projectData == null) {
|
||||
throw new IOException(
|
||||
"Failed to view specified project/repository: " + GhidraURL.getDisplayString(url));
|
||||
}
|
||||
url = projectData.getProjectLocator().getURL(); // transform to repository root URL
|
||||
|
||||
otherViews.put(url, projectData);
|
||||
otherViewsMap.put(url, projectData);
|
||||
return projectData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectData addProjectView(URL url, boolean visible) throws IOException {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
if (isClosed) {
|
||||
throw new IOException("project is closed");
|
||||
}
|
||||
|
@ -322,7 +291,7 @@ public class DefaultProject implements Project {
|
|||
throw new IOException("Invalid Ghidra URL specified: " + url);
|
||||
}
|
||||
|
||||
ProjectData projectData = otherViews.get(url);
|
||||
ProjectData projectData = otherViewsMap.get(url);
|
||||
if (projectData == null) {
|
||||
projectData = openProjectView(url);
|
||||
}
|
||||
|
@ -340,13 +309,13 @@ public class DefaultProject implements Project {
|
|||
*/
|
||||
@Override
|
||||
public void removeProjectView(URL url) {
|
||||
synchronized (otherViews) {
|
||||
ProjectFileManager dataMgr = otherViews.remove(url);
|
||||
synchronized (otherViewsMap) {
|
||||
DefaultProjectData dataMgr = otherViewsMap.remove(url);
|
||||
if (dataMgr != null) {
|
||||
if (visibleViews.remove(url)) {
|
||||
notifyVisibleViewRemoved(url);
|
||||
}
|
||||
dataMgr.dispose();
|
||||
dataMgr.close();
|
||||
Msg.info(this, "Closed project view: " + GhidraURL.getDisplayString(url));
|
||||
changed = true;
|
||||
}
|
||||
|
@ -401,22 +370,20 @@ public class DefaultProject implements Project {
|
|||
|
||||
@Override
|
||||
public RepositoryAdapter getRepository() {
|
||||
return fileMgr.getRepository();
|
||||
return projectData.getRepository();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
isClosed = true;
|
||||
|
||||
Iterator<ProjectFileManager> iter = otherViews.values().iterator();
|
||||
while (iter.hasNext()) {
|
||||
ProjectFileManager dataMgr = iter.next();
|
||||
for (DefaultProjectData dataMgr : otherViewsMap.values()) {
|
||||
if (dataMgr != null) {
|
||||
dataMgr.dispose();
|
||||
dataMgr.close();
|
||||
}
|
||||
}
|
||||
otherViews.clear();
|
||||
otherViewsMap.clear();
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -429,7 +396,7 @@ public class DefaultProject implements Project {
|
|||
}
|
||||
}
|
||||
finally {
|
||||
fileMgr.dispose();
|
||||
projectData.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,7 +416,7 @@ public class DefaultProject implements Project {
|
|||
@Override
|
||||
public void restore() {
|
||||
// if there is a saved project, restore it
|
||||
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
|
||||
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
|
||||
String errorMsg = null;
|
||||
Throwable error = null;
|
||||
try {
|
||||
|
@ -593,7 +560,7 @@ public class DefaultProject implements Project {
|
|||
try {
|
||||
// save tool state
|
||||
root.addContent(toolManager.saveToXml()); // the tool manager will save the open tools' state
|
||||
File saveFile = new File(fileMgr.getProjectDir(), PROJECT_STATE);
|
||||
File saveFile = new File(projectData.getProjectDir(), PROJECT_STATE);
|
||||
OutputStream os = new FileOutputStream(saveFile);
|
||||
Document doc = new Document(root);
|
||||
XMLOutputter xmlOut = new GenericXMLOutputter();
|
||||
|
@ -629,15 +596,14 @@ public class DefaultProject implements Project {
|
|||
@Override
|
||||
public List<DomainFile> getOpenData() {
|
||||
ArrayList<DomainFile> openFiles = new ArrayList<>();
|
||||
fileMgr.findOpenFiles(openFiles);
|
||||
projectData.findOpenFiles(openFiles);
|
||||
ProjectData[] viewedProjs = getViewedProjectData();
|
||||
for (ProjectData viewedProj : viewedProjs) {
|
||||
((ProjectFileManager) viewedProj).findOpenFiles(openFiles);
|
||||
((DefaultProjectData) viewedProj).findOpenFiles(openFiles);
|
||||
}
|
||||
List<DomainFile> list = new ArrayList<>();
|
||||
TransientDataManager.getTransients(list);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
DomainFile df = list.get(i);
|
||||
for (DomainFile df : list) {
|
||||
if (df != null && df.isOpen()) {
|
||||
openFiles.add(df);
|
||||
}
|
||||
|
@ -646,8 +612,8 @@ public class DefaultProject implements Project {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ProjectFileManager getProjectData() {
|
||||
return fileMgr;
|
||||
public DefaultProjectData getProjectData() {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -662,12 +628,12 @@ public class DefaultProject implements Project {
|
|||
|
||||
@Override
|
||||
public ProjectData getProjectData(ProjectLocator locator) {
|
||||
if (locator.equals(fileMgr.getProjectLocator())) {
|
||||
return fileMgr;
|
||||
if (locator.equals(projectData.getProjectLocator())) {
|
||||
return projectData;
|
||||
}
|
||||
|
||||
synchronized (otherViews) {
|
||||
for (ProjectData data : otherViews.values()) {
|
||||
synchronized (otherViewsMap) {
|
||||
for (ProjectData data : otherViewsMap.values()) {
|
||||
if (locator.equals(data.getProjectLocator())) {
|
||||
return data;
|
||||
}
|
||||
|
@ -680,30 +646,30 @@ public class DefaultProject implements Project {
|
|||
@Override
|
||||
public ProjectData getProjectData(URL url) {
|
||||
if (projectLocator.getURL().equals(url)) {
|
||||
return fileMgr;
|
||||
return projectData;
|
||||
}
|
||||
URL remoteURL = getProjectData().getRootFolder().getSharedProjectURL();
|
||||
if (remoteURL != null) {
|
||||
remoteURL = GhidraURL.getProjectURL(url);
|
||||
}
|
||||
if (remoteURL.equals(url)) {
|
||||
return fileMgr;
|
||||
return projectData;
|
||||
}
|
||||
|
||||
synchronized (otherViews) {
|
||||
return otherViews.get(url);
|
||||
synchronized (otherViewsMap) {
|
||||
return otherViewsMap.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectData[] getViewedProjectData() {
|
||||
synchronized (otherViews) {
|
||||
synchronized (otherViewsMap) {
|
||||
|
||||
// only return visible viewed project
|
||||
List<ProjectData> list = new ArrayList<>();
|
||||
for (URL url : otherViews.keySet()) {
|
||||
for (URL url : otherViewsMap.keySet()) {
|
||||
if (visibleViews.contains(url)) {
|
||||
list.add(otherViews.get(url));
|
||||
list.add(otherViewsMap.get(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -715,11 +681,9 @@ public class DefaultProject implements Project {
|
|||
|
||||
@Override
|
||||
public void releaseFiles(Object consumer) {
|
||||
fileMgr.releaseDomainFiles(consumer);
|
||||
synchronized (otherViews) {
|
||||
Iterator<ProjectFileManager> it = otherViews.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
ProjectFileManager mgr = it.next();
|
||||
projectData.releaseDomainFiles(consumer);
|
||||
synchronized (otherViewsMap) {
|
||||
for (DefaultProjectData mgr : otherViewsMap.values()) {
|
||||
mgr.releaseDomainFiles(consumer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ public class DefaultGhidraProtocolConnector extends GhidraProtocolConnector {
|
|||
}
|
||||
catch (RepositoryNotFoundException e) {
|
||||
statusCode = StatusCode.NOT_FOUND;
|
||||
return statusCode;
|
||||
}
|
||||
}
|
||||
else if (!repositoryServerAdapter.isCancelled()) {
|
||||
|
|
|
@ -21,7 +21,7 @@ import java.net.URL;
|
|||
|
||||
import ghidra.framework.client.NotConnectedException;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
|
||||
import ghidra.framework.store.LockException;
|
||||
|
@ -126,13 +126,13 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
|
|||
* @return project data instance or null if project not found
|
||||
* @throws IOException if IO error occurs
|
||||
*/
|
||||
ProjectFileManager getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
||||
DefaultProjectData getLocalProjectData(boolean readOnlyAccess) throws IOException {
|
||||
if (connect(readOnlyAccess) != StatusCode.OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new ProjectFileManager(localStorageLocator, !readOnlyAccess, false);
|
||||
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
|
||||
}
|
||||
catch (NotOwnerException | ReadOnlyException e) {
|
||||
statusCode = StatusCode.UNAUTHORIZED;
|
||||
|
|
|
@ -19,7 +19,7 @@ import java.io.*;
|
|||
import java.net.*;
|
||||
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectData;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
|
@ -69,38 +69,6 @@ public class GhidraURLConnection extends URLConnection {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: consider implementing request and response headers
|
||||
|
||||
// /**
|
||||
// * Ghidra Status-Code 200: OK.
|
||||
// */
|
||||
// public static final int GHIDRA_OK = 200;
|
||||
//
|
||||
// /**
|
||||
// * Ghidra Status-Code 401: Unauthorized.
|
||||
// * This response code includes a variety of connection errors
|
||||
// * which are reported/logged by the Ghidra Server support code.
|
||||
// */
|
||||
// public static final int GHIDRA_UNAUTHORIZED = 401;
|
||||
//
|
||||
// /**
|
||||
// * Ghidra Status-Code 404: Not Found.
|
||||
// */
|
||||
// public static final int GHIDRA_NOT_FOUND = 404;
|
||||
//
|
||||
// /**
|
||||
// * Ghidra Status-Code 423: Locked
|
||||
// * Caused by attempt to open local project data with write-access when project is
|
||||
// * already opened and locked.
|
||||
// */
|
||||
// public static final int GHIDRA_LOCKED = 423;
|
||||
//
|
||||
// /**
|
||||
// * Ghidra Status-Code 503: Unavailable
|
||||
// * Caused by other connection failure
|
||||
// */
|
||||
// public static final int GHIDRA_UNAVAILABLE = 503;
|
||||
|
||||
/**
|
||||
* Ghidra content type - domain folder/file wrapped within GhidraURLWrappedContent object.
|
||||
* @see GhidraURLWrappedContent
|
||||
|
@ -117,7 +85,7 @@ public class GhidraURLConnection extends URLConnection {
|
|||
|
||||
private GhidraProtocolConnector protocolConnector;
|
||||
|
||||
private ProjectFileManager projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private Object refObject;
|
||||
|
||||
private boolean readOnly = true;
|
||||
|
@ -270,12 +238,17 @@ public class GhidraURLConnection extends URLConnection {
|
|||
}
|
||||
|
||||
/**
|
||||
* If URL connects and corresponds to a valid repository, this method
|
||||
* If URL connects and corresponds to a valid repository or local project, this method
|
||||
* may be used to obtain the associated ProjectData object. The caller is
|
||||
* responsible for closing the returned project data when no longer in-use,
|
||||
* failure to do so may prevent release of repository handle to server.
|
||||
* Only a single call to this method is permitted.
|
||||
* @return transient project data or null if unavailable
|
||||
* responsible for properly {@link ProjectData#close() closing} the returned project data
|
||||
* instance when no longer in-use, failure to do so may prevent release of repository handle
|
||||
* to server until process exits. It is important that {@link ProjectData#close()} is
|
||||
* invoked once, and only once, per call to this method to ensure project "use" tracking
|
||||
* is properly maintained. Improperly invoking the close method on a shared transient
|
||||
* {@link ProjectData} instance may cause the underlying storage to be prematurely
|
||||
* disposed.
|
||||
*
|
||||
* @return project data which corresponds to this connection or null if unavailable
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public ProjectData getProjectData() throws IOException {
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.InvalidNameException;
|
||||
|
||||
|
@ -43,7 +44,7 @@ public class GhidraURLWrappedContent {
|
|||
|
||||
private List<Object> consumers = new ArrayList<Object>();
|
||||
|
||||
private ProjectData projectData;
|
||||
private DefaultProjectData projectData;
|
||||
private Object refObject;
|
||||
|
||||
public GhidraURLWrappedContent(GhidraURLConnection c) {
|
||||
|
@ -82,6 +83,8 @@ public class GhidraURLWrappedContent {
|
|||
|
||||
/**
|
||||
* Close associated {@link ProjectData} when all consumers have released wrapped object.
|
||||
* Underlying project data instance may remain active until all open project files have been
|
||||
* released/closed.
|
||||
*/
|
||||
private void closeProjectData() {
|
||||
if (projectData != null) {
|
||||
|
@ -91,8 +94,8 @@ public class GhidraURLWrappedContent {
|
|||
refObject = null;
|
||||
}
|
||||
|
||||
private DomainFolder getExplicitFolder(String folderPath) throws InvalidNameException,
|
||||
IOException {
|
||||
private DomainFolder getExplicitFolder(String folderPath)
|
||||
throws InvalidNameException, IOException {
|
||||
DomainFolder folder = projectData.getRootFolder();
|
||||
for (String name : folderPath.substring(1).split("/")) {
|
||||
DomainFolder subfolder = folder.getFolder(name);
|
||||
|
@ -110,7 +113,7 @@ public class GhidraURLWrappedContent {
|
|||
return;
|
||||
}
|
||||
|
||||
projectData = c.getProjectData();
|
||||
projectData = (DefaultProjectData) c.getProjectData();
|
||||
|
||||
String folderItemName = c.getFolderItemName();
|
||||
String folderPath = c.getFolderPath();
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
package ghidra.framework.protocol.ghidra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import generic.timer.GhidraSwinglessTimer;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.data.ProjectFileManager;
|
||||
import ghidra.framework.data.DefaultProjectData;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.framework.remote.RepositoryHandle;
|
||||
import ghidra.framework.store.LockException;
|
||||
|
@ -27,7 +28,7 @@ import ghidra.util.Msg;
|
|||
import ghidra.util.SystemUtilities;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
public class TransientProjectData extends ProjectFileManager {
|
||||
public class TransientProjectData extends DefaultProjectData {
|
||||
|
||||
private TransientProjectManager dataMgr;
|
||||
final RepositoryInfo repositoryInfo;
|
||||
|
@ -146,23 +147,30 @@ public class TransientProjectData extends ProjectFileManager {
|
|||
}
|
||||
stopCleanupTimer();
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
Msg.debug(this, "Removing transient project (" + repositoryInfo.toShortString() + "): " +
|
||||
getProjectLocator().getProjectDir());
|
||||
String msgTail = " transient project (" + repositoryInfo.toShortString() + "): " +
|
||||
getProjectLocator().getProjectDir() + ", URL: " + repositoryInfo.getURL();
|
||||
if (instanceUseCount != 0) {
|
||||
Msg.error(this, "Premature removal of active" + msgTail);
|
||||
}
|
||||
else {
|
||||
Msg.debug(this, "Removing idle" + msgTail);
|
||||
}
|
||||
}
|
||||
|
||||
dataMgr.cleanupProjectData(repositoryInfo, this);
|
||||
|
||||
super.dispose(); // disconnects repository
|
||||
|
||||
// TODO: There could still be open files if they have not been properly released/closed !!
|
||||
// Remove temporary project storage
|
||||
// NOTE: This could be affected by project files which still remain open
|
||||
ProjectLocator locator = getProjectLocator();
|
||||
FileUtilities.deleteDir(locator.getProjectDir());
|
||||
locator.getMarkerFile().delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
public void close() {
|
||||
// prevent normal disposal - rely on finalizer and shutdown hook
|
||||
synchronized (cleanupTimer) {
|
||||
if (instanceUseCount == 0) {
|
||||
|
@ -178,13 +186,18 @@ public class TransientProjectData extends ProjectFileManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
forcedDispose();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// ignore errors during finalize
|
||||
}
|
||||
super.finalize();
|
||||
protected void dispose() {
|
||||
// rely on forcedDispose to invoke super.dispose()
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
return repositoryInfo.getURL();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -71,8 +71,9 @@ public class TransientProjectManager {
|
|||
}
|
||||
|
||||
private TransientProjectManager() {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread((Runnable) () -> dispose(), "TransientProjectManager Shutdown Hook"));
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(new Thread((Runnable) () -> dispose(),
|
||||
"TransientProjectManager Shutdown Hook"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,8 +81,6 @@ public class TransientProjectManager {
|
|||
* connections. WARNING: This method intended for testing only.
|
||||
*/
|
||||
public synchronized void dispose() {
|
||||
// TODO: server handles may be shared with non-transient projects
|
||||
|
||||
TransientProjectData[] projectDataArray =
|
||||
repositoryMap.values().toArray(new TransientProjectData[repositoryMap.size()]);
|
||||
for (TransientProjectData projectData : projectDataArray) {
|
||||
|
|
|
@ -15,17 +15,16 @@
|
|||
*/
|
||||
package ghidra.framework.task;
|
||||
|
||||
import generic.concurrent.GThreadPool;
|
||||
import ghidra.framework.model.DomainObjectClosedListener;
|
||||
import ghidra.framework.model.UndoableDomainObject;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import generic.concurrent.GThreadPool;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Class for managing a queue of tasks to be executed, one at a time, in priority order. All the
|
||||
* tasks pertain to an UndoableDomainObject and transactions are created on the UndoableDomainObject
|
||||
|
@ -95,7 +94,8 @@ public class GTaskManager {
|
|||
|
||||
domainObject.addCloseListener(new DomainObjectClosedListener() {
|
||||
@Override
|
||||
public void domainObjectClosed() {
|
||||
public void domainObjectClosed(DomainObject dobj) {
|
||||
// assert dobj == domainObj
|
||||
GTaskManagerFactory.domainObjectClosed(domainObject);
|
||||
domainObject = null;
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ public class GTaskManager {
|
|||
*
|
||||
* @param task the task to be run.
|
||||
* @param priority the priority of the task. Lower numbers are run before higher numbers.
|
||||
* @param useCurrentGroup. If true, this task will be rolled into the current transaction group
|
||||
* @param useCurrentGroup If true, this task will be rolled into the current transaction group
|
||||
* if one exists. If false, any open transaction
|
||||
* will be closed and a new transaction will be opened before
|
||||
* this task is run.
|
||||
|
@ -680,7 +680,8 @@ public class GTaskManager {
|
|||
taskListener.taskCompleted(task, result);
|
||||
}
|
||||
catch (Throwable unexpected) {
|
||||
Msg.error(this, "Unexpected exception notifying listener of task completed", unexpected);
|
||||
Msg.error(this, "Unexpected exception notifying listener of task completed",
|
||||
unexpected);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -705,7 +706,8 @@ public class GTaskManager {
|
|||
taskListener.taskScheduled(scheduledTask);
|
||||
}
|
||||
catch (Throwable unexpected) {
|
||||
Msg.error(this, "Unexpected exception notifying listener of task scheduled", unexpected);
|
||||
Msg.error(this, "Unexpected exception notifying listener of task scheduled",
|
||||
unexpected);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package ghidra.framework.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
|
@ -95,6 +96,18 @@ public class TestDummyProjectData implements ProjectData {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSharedProjectURL() {
|
||||
// stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getLocalProjectURL() {
|
||||
// stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDomainFolderChangeListener(DomainFolderChangeListener listener) {
|
||||
// stub
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue