GP-1200 - Fixed UI live lock when connecting to remote project

This commit is contained in:
dragonmacher 2021-08-18 15:42:38 -04:00
parent 58ceda2265
commit c0ecb797e7
20 changed files with 276 additions and 292 deletions

View file

@ -19,7 +19,7 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import ghidra.framework.model.*;
import ghidra.util.SystemUtilities;
import ghidra.util.Swing;
class DomainFolderChangeListenerList implements DomainFolderChangeListener {
@ -47,7 +47,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderAdded(folder);
}
@ -60,7 +60,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileAdded(file);
}
@ -73,7 +73,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderRemoved(parent, name);
}
@ -87,7 +87,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileRemoved(parent, name, fileID);
}
@ -100,7 +100,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderRenamed(folder, oldName);
}
@ -113,7 +113,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileRenamed(file, oldName);
}
@ -126,7 +126,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderMoved(folder, oldParent);
}
@ -140,7 +140,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileMoved(file, oldParent, oldName);
}
@ -153,7 +153,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderSetActive(folder);
}
@ -166,7 +166,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileStatusChanged(file, fileIDset);
}
@ -179,7 +179,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectOpenedForUpdate(file, object);
}
@ -192,7 +192,7 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
Swing.runLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectClosed(file, object);
}
@ -205,12 +205,11 @@ class DomainFolderChangeListenerList implements DomainFolderChangeListener {
if (list.isEmpty()) {
return;
}
Runnable r = () -> {
Swing.runNow(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectReplaced(file, oldObject);
}
};
SystemUtilities.runSwingNow(r);
});
}
public void clearAll() {

View file

@ -512,7 +512,7 @@ public class GhidraFile implements DomainFile {
public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (dobj == null) {
return new ArrayList<Object>();
return new ArrayList<>();
}
return dobj.getConsumerList();
}
@ -554,7 +554,7 @@ public class GhidraFile implements DomainFile {
catch (IOException e) {
fileError(e);
}
return new HashMap<String, String>();
return new HashMap<>();
}
void fileChanged() {

View file

@ -85,7 +85,8 @@ public class GhidraFolder implements DomainFolder {
GhidraFolderData getFolderPathData(String folderPath) throws FileNotFoundException {
GhidraFolderData parentData = (folderPath.startsWith(FileSystem.SEPARATOR))
? fileManager.getRootFolderData() : getFolderData();
? fileManager.getRootFolderData()
: getFolderData();
GhidraFolderData folderData = parentData.getFolderPathData(folderPath, false);
if (folderData == null) {
String path = (folderPath.startsWith(FileSystem.SEPARATOR)) ? folderPath

View file

@ -24,16 +24,14 @@ 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;
import ghidra.framework.store.FileSystemListener;
import ghidra.framework.store.FolderItem;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.remote.RemoteFileSystem;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateFileException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.*;
import utilities.util.FileUtilities;
/**
@ -143,9 +141,6 @@ public class ProjectFileManager implements ProjectData {
}
}
/**
* Constructor used for testing
*/
ProjectFileManager(LocalFileSystem fileSystem, FileSystem versionedFileSystem) {
this.localStorageLocator = new ProjectLocator(null, "Test");
owner = SystemUtilities.getUserName();
@ -208,9 +203,6 @@ public class ProjectFileManager implements ProjectData {
fileSystem.testValidName(name, isPath);
}
/**
* @see ghidra.framework.model.ProjectData#getUser()
*/
@Override
public User getUser() {
if (repository != null) {
@ -386,17 +378,11 @@ public class ProjectFileManager implements ProjectData {
return owner;
}
/**
* @see ghidra.framework.model.ProjectData#getRootFolder()
*/
@Override
public GhidraFolder getRootFolder() {
return rootFolderData.getDomainFolder();
}
/**
* @see ghidra.framework.model.ProjectData#getFolder(java.lang.String)
*/
@Override
public DomainFolder getFolder(String path) {
int len = path.length();
@ -429,9 +415,8 @@ public class ProjectFileManager implements ProjectData {
}
}
// NOTE: we can't distinguish between files represented
// in both file counts so we will return the larger of
// the two counts obtained.
// NOTE: we can't distinguish between files represented in both file counts so we will
// return the larger of the two counts obtained.
int privateFileCnt = -1;
try {
@ -452,9 +437,6 @@ public class ProjectFileManager implements ProjectData {
return Math.max(sharedFileCnt, privateFileCnt);
}
/**
* @see ghidra.framework.model.ProjectData#getFile(java.lang.String)
*/
@Override
public DomainFile getFile(String path) {
int len = path.length();
@ -480,17 +462,11 @@ public class ProjectFileManager implements ProjectData {
return null;
}
/**
* @see ghidra.framework.model.ProjectData#getFileByID(java.lang.String)
*/
@Override
public DomainFile getFileByID(String fileID) {
return fileIndex.getFileByID(fileID);
}
/**
* @see ghidra.framework.model.ProjectData#getSharedFileURL(java.lang.String)
*/
@Override
public URL getSharedFileURL(String path) {
if (repository != null) {
@ -504,10 +480,6 @@ public class ProjectFileManager implements ProjectData {
return null;
}
/**
* Releases all domain files for the specified consumer.
* @param consumer the domain object consumer
*/
public void releaseDomainFiles(Object consumer) {
for (DomainObjectAdapter domainObj : openDomainObjects.values()) {
try {
@ -522,8 +494,7 @@ public class ProjectFileManager implements ProjectData {
}
/**
* Finds all changed domain files and appends
* them to the specified list.
* Finds all changed domain files and appends them to the specified list.
* @param list the list to receive the changed domain files
*/
@Override
@ -533,52 +504,30 @@ public class ProjectFileManager implements ProjectData {
}
}
/**
* @see ghidra.framework.model.ProjectData#getProjectLocator()
*/
@Override
public ProjectLocator getProjectLocator() {
return localStorageLocator;
}
/**
* @see ghidra.framework.model.ProjectData#addDomainFolderChangeListener(
* ghidra.framework.model.DomainFolderChangeListener)
*/
@Override
public void addDomainFolderChangeListener(DomainFolderChangeListener l) {
listenerList.addListener(l);
}
/**
* @see ghidra.framework.model.ProjectData#removeDomainFolderChangeListener(
* ghidra.framework.model.DomainFolderChangeListener)
*/
@Override
public void removeDomainFolderChangeListener(DomainFolderChangeListener l) {
listenerList.removeListener(l);
}
/**
* Returns the private files system associated with this project file manager.
* @return the private files system associated with this project file manager
*/
public FileSystem getPrivateFileSystem() {
return fileSystem;
}
/**
* Returns the repository associated with this project file manager.
* @return the repository associated with this project file manager
*/
@Override
public RepositoryAdapter getRepository() {
return repository;
}
/**
* @see ghidra.framework.model.ProjectData#refresh(boolean)
*/
@Override
public void refresh(boolean force) throws IOException {
try {
@ -589,12 +538,6 @@ public class ProjectFileManager implements ProjectData {
}
}
/*
* (non-Javadoc)
* @see ghidra.framework.model.ProjectData#convertProjectToShared(
* ghidra.framework.client.RepositoryAdapter,
* ghidra.util.task.TaskMonitor)
*/
@Override
public void convertProjectToShared(RepositoryAdapter newRepository, TaskMonitor monitor)
throws IOException, CancelledException {
@ -607,7 +550,7 @@ public class ProjectFileManager implements ProjectData {
throw new IllegalStateException("Only private project may be converted to shared");
}
// 1) Convert versioned files (inclulding checked-out files) to private files
// 1) Convert versioned files (including checked-out files) to private files
convertFilesToPrivate(getRootFolder(), monitor);
// 2) Update the properties with server info
@ -621,12 +564,6 @@ public class ProjectFileManager implements ProjectData {
FileUtilities.deleteDir(versionedFileSystemDir);
}
/*
* (non-Javadoc)
* @see ghidra.framework.model.ProjectData#updateRepositoryInfo(
* ghidra.framework.client.RepositoryAdapter,
* ghidra.util.task.TaskMonitor)
*/
@Override
public void updateRepositoryInfo(RepositoryAdapter newRepository, TaskMonitor monitor)
throws IOException, CancelledException {
@ -641,20 +578,20 @@ public class ProjectFileManager implements ProjectData {
throws IOException, CancelledException {
DomainFile[] files = folder.getFiles();
for (int i = 0; i < files.length; i++) {
for (DomainFile file : files) {
if (monitor.isCancelled()) {
throw new CancelledException();
}
if (files[i].isCheckedOut()) {
throw new IOException("File " + files[i].getPathname() + " is checked out.");
if (file.isCheckedOut()) {
throw new IOException("File " + file.getPathname() + " is checked out.");
}
}
DomainFolder[] folders = folder.getFolders();
for (int i = 0; i < folders.length; i++) {
for (DomainFolder folder2 : folders) {
if (monitor.isCancelled()) {
throw new CancelledException();
}
findCheckedOutFiles(folders[i], monitor);
findCheckedOutFiles(folder2, monitor);
}
}
@ -662,12 +599,12 @@ public class ProjectFileManager implements ProjectData {
throws IOException, CancelledException {
DomainFile[] files = folder.getFiles();
for (int i = 0; i < files.length; i++) {
((GhidraFile) files[i]).convertToPrivateFile(monitor);
for (DomainFile file : files) {
((GhidraFile) file).convertToPrivateFile(monitor);
}
DomainFolder[] folders = folder.getFolders();
for (int i = 0; i < folders.length; i++) {
convertFilesToPrivate(folders[i], monitor);
for (DomainFolder folder2 : folders) {
convertFilesToPrivate(folder2, monitor);
}
}
@ -902,12 +839,32 @@ public class ProjectFileManager implements ProjectData {
@Override
public void syncronize() {
if (SystemUtilities.isInHeadlessMode()) {
doSynchronize();
return;
}
try {
FileSystemSynchronizer.setSynchronizing(true);
// This operation can hold a lock for a long period. Block with a modal dialog to
// prevent UI live lock situations.
TaskLauncher.launchModal("Synchronizing Filesystem", this::doSynchronize);
}
finally {
FileSystemSynchronizer.setSynchronizing(false);
}
}
private void doSynchronize() {
try {
rootFolderData.refresh(true, true, projectDisposalMonitor);
scheduleUserDataReconcilation();
}
catch (Exception e) {
// ignore
Msg.trace(this, "Exception synchronizing filesystem", e);
}
}
}
@ -929,9 +886,6 @@ public class ProjectFileManager implements ProjectData {
return buf.length() == 0 ? "unknown" : buf.toString();
}
/**
* Return the project directory.
*/
public File getProjectDir() {
return projectDir;
}
@ -941,9 +895,6 @@ public class ProjectFileManager implements ProjectData {
dispose();
}
/**
* Disposes this project file manager.
*/
public void dispose() {
synchronized (this) {
@ -978,12 +929,11 @@ public class ProjectFileManager implements ProjectData {
/**
* Set the open domain object (opened for update) associated with a file.
* NOTE: Caller is responsible for setting domain file on domain object after
* invoking this method.
* If a domain object saveAs was done, the previous file association
* NOTE: Caller is responsible for setting domain file on domain object after invoking this
* method. If a domain object saveAs was done, the previous file association
* will be removed.
* @param pathname
* @param doa
* @param pathname the path name
* @param doa the domain object
*/
synchronized void setDomainObject(String pathname, DomainObjectAdapter doa) {
if (openDomainObjects.containsKey(pathname)) {
@ -998,7 +948,8 @@ public class ProjectFileManager implements ProjectData {
/**
* Returns the open domain object (opened for update) for the specified path.
* @param pathname
* @param pathname the path name
* @return the domain object
*/
synchronized DomainObjectAdapter getOpenedDomainObject(String pathname) {
return openDomainObjects.get(pathname);
@ -1006,7 +957,7 @@ public class ProjectFileManager implements ProjectData {
/**
* Clears the previously open domain object which has been closed.
* @param pathname
* @param pathname the path name
* @return true if previously open domain file was cleared, else false
*/
synchronized boolean clearDomainObject(String pathname) {
@ -1028,7 +979,7 @@ public class ProjectFileManager implements ProjectData {
/**
* Remove specified fileID from index.
* @param fileID
* @param fileID the file ID
*/
public void removeFromIndex(String fileID) {
fileIndex.removeFileEntry(fileID);
@ -1041,5 +992,4 @@ public class ProjectFileManager implements ProjectData {
public TaskMonitor getProjectDisposalMonitor() {
return projectDisposalMonitor;
}
}

View file

@ -22,8 +22,10 @@ import docking.widgets.OptionDialog;
import ghidra.framework.client.*;
import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.main.datatable.DomainFileProviderContextAction;
import ghidra.framework.model.*;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.Project;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.FileSystemSynchronizer;
import ghidra.util.HelpLocation;
/**
@ -80,6 +82,14 @@ public abstract class VersionControlAction extends DomainFileProviderContextActi
}
}
/**
* True if the file system is locked by another thread for a long running operation
* @return true if locked
*/
protected boolean isFileSystemBusy() {
return FileSystemSynchronizer.isSynchronizing();
}
/**
* NOTE: do not call this from a non-Swing thread.
* @return true if the repository is null or is connected.

View file

@ -57,11 +57,12 @@ public class VersionControlAddAction extends VersionControlAction {
addToVersionControl(context.getSelectedFiles());
}
/**
* Returns true if at least one of the provided domain files can be added to the repository.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> domainFiles = context.getSelectedFiles();
for (DomainFile domainFile : domainFiles) {
if (domainFile.canAddToRepository()) {
@ -71,11 +72,6 @@ public class VersionControlAddAction extends VersionControlAction {
return false;
}
/**
* Adds all the non-version controlled domain files to the repository from the
* list of files from the DomainFileProvider.
* @param domainFiles
*/
private void addToVersionControl(List<DomainFile> domainFiles) {
if (!checkRepositoryConnected()) {

View file

@ -61,12 +61,12 @@ public class VersionControlCheckInAction extends VersionControlAction {
doCheckIn(context.getSelectedFiles());
}
/**
* Returns true if at least one of the provided domain files can have its changes
* checked into the repository.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> domainFiles = context.getSelectedFiles();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isCheckedOut() && domainFile.modifiedSinceCheckout()) {
@ -83,7 +83,7 @@ public class VersionControlCheckInAction extends VersionControlAction {
if (!checkRepositoryConnected()) {
return;
}
List<DomainFile> checkedOut = new ArrayList<DomainFile>();
List<DomainFile> checkedOut = new ArrayList<>();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isCheckedOut() && domainFile.modifiedSinceCheckout()) {
checkedOut.add(domainFile);
@ -112,8 +112,8 @@ public class VersionControlCheckInAction extends VersionControlAction {
return;
}
ArrayList<DomainFile> changedList = new ArrayList<DomainFile>();
ArrayList<DomainFile> list = new ArrayList<DomainFile>();
ArrayList<DomainFile> changedList = new ArrayList<>();
ArrayList<DomainFile> list = new ArrayList<>();
for (int i = 0; i < fileList.size(); i++) {
DomainFile df = fileList.get(i);
if (df != null && df.canCheckin()) {

View file

@ -64,12 +64,12 @@ public class VersionControlCheckOutAction extends VersionControlAction {
checkOut(context.getSelectedFiles());
}
/**
* Returns true if at least one of the provided domain files can can be
* checked out of the repository.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> providedList = context.getSelectedFiles();
for (DomainFile domainFile : providedList) {
if (domainFile.canCheckout()) {

View file

@ -48,15 +48,16 @@ public class VersionControlShowHistoryAction extends VersionControlAction {
showHistory(context.getSelectedFiles());
}
/**
* Returns true if a single version controlled domain file is being provided.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
List<DomainFile> domainFiles = context.getSelectedFiles();
if (domainFiles.size() != 1) {
return false;
}
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
DomainFile domainFile = domainFiles.get(0);
return domainFile.isVersioned();
}

View file

@ -61,11 +61,12 @@ public class VersionControlUndoCheckOutAction extends VersionControlAction {
undoCheckOut(context.getSelectedFiles());
}
/**
* Returns true if at least one of the provided domain files is checked out from the repository.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> domainFiles = context.getSelectedFiles();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isCheckedOut()) {
@ -83,8 +84,8 @@ public class VersionControlUndoCheckOutAction extends VersionControlAction {
if (!checkRepositoryConnected()) {
return;
}
List<DomainFile> unmodifiedCheckOutsList = new ArrayList<DomainFile>();
List<DomainFile> modifiedCheckOutsList = new ArrayList<DomainFile>();
List<DomainFile> unmodifiedCheckOutsList = new ArrayList<>();
List<DomainFile> modifiedCheckOutsList = new ArrayList<>();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isCheckedOut()) {
if (domainFile.modifiedSinceCheckout()) {
@ -145,10 +146,9 @@ public class VersionControlUndoCheckOutAction extends VersionControlAction {
/**
* Creates a task for undoing checkouts of domain files.
* @param unmodifiedCheckOutsList the list of unmodified checked out files
* @param modifiedCheckOutsList the list of checked out files that have been modified
* @param modifiedCheckedOutFiles the list of checked out files that have been modified
* @param saveCopy true indicates that copies of the modified files should be made
* before undo of the checkout.
* @param listener the task listener to call when the task completes or is cancelled.
* before undo of the checkout
*/
UndoCheckOutTask(List<DomainFile> unmodifiedCheckOutsList,
DomainFile[] modifiedCheckedOutFiles, boolean saveCopy) {
@ -158,9 +158,6 @@ public class VersionControlUndoCheckOutAction extends VersionControlAction {
this.saveCopy = saveCopy;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {

View file

@ -55,11 +55,12 @@ public class VersionControlUndoHijackAction extends VersionControlAction {
undoHijackedFiles(context.getSelectedFiles());
}
/**
* Returns true if at least one of the provided domain files is hijacked.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> domainFiles = context.getSelectedFiles();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isHijacked()) {
@ -77,7 +78,7 @@ public class VersionControlUndoHijackAction extends VersionControlAction {
return;
}
List<DomainFile> hijackList = new ArrayList<DomainFile>();
List<DomainFile> hijackList = new ArrayList<>();
for (DomainFile domainFile : domainFiles) {
if (domainFile != null && domainFile.isHijacked()) {
hijackList.add(domainFile);
@ -143,8 +144,7 @@ public class VersionControlUndoHijackAction extends VersionControlAction {
* Creates a task for undoing hijacks of domain files.
* @param hijackFiles the list of hijacked files
* @param saveCopy true indicates that copies of the modified files should be made
* before undo of the checkout.
* @param listener the task listener to call when the task completes or is cancelled.
* before undo of the checkout
*/
UndoHijackTask(DomainFile[] hijackFiles, boolean saveCopy) {
super("Undo Hijack", true, true, true);
@ -152,9 +152,6 @@ public class VersionControlUndoHijackAction extends VersionControlAction {
this.saveCopy = saveCopy;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {

View file

@ -59,11 +59,12 @@ public class VersionControlUpdateAction extends VersionControlAction {
update(context.getSelectedFiles());
}
/**
* Returns true if at least one checked out file has a newer version in the repository.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
List<DomainFile> providedList = context.getSelectedFiles();
for (DomainFile domainFile : providedList) {
if (domainFile.isVersioned() &&
@ -83,7 +84,7 @@ public class VersionControlUpdateAction extends VersionControlAction {
return;
}
List<DomainFile> updateList = new ArrayList<DomainFile>();
List<DomainFile> updateList = new ArrayList<>();
for (DomainFile domainFile : domainFiles) {
if (domainFile != null && domainFile.canMerge()) {
if (!canCloseDomainFile(domainFile)) {

View file

@ -50,15 +50,16 @@ public class VersionControlViewCheckOutAction extends VersionControlAction {
viewCheckouts(context.getSelectedFiles());
}
/**
* Returns true if a single version controlled domain file is being provided.
*/
@Override
public boolean isEnabledForContext(DomainFileContext context) {
List<DomainFile> domainFiles = context.getSelectedFiles();
if (domainFiles.size() != 1) {
return false;
}
if (isFileSystemBusy()) {
return false; // don't block; we should get called again later
}
DomainFile domainFile = domainFiles.get(0);
return domainFile.isVersioned();
}

View file

@ -16,7 +16,7 @@
package ghidra.framework.model;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
@ -28,10 +28,10 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* <code>DomainFile</code> provides a storage interface for project
* files. A <code>DomainFile</code> is an immutable reference to
* a file contained within a project. The state of a <code>DomainFile</code>
* object does not track name/parent changes made to the referenced project file.
* <code>DomainFile</code> provides a storage interface for project files. A
* <code>DomainFile</code> is an immutable reference to file contained within a project. The state
* of a <code>DomainFile</code> object does not track name/parent changes made to the referenced
* project file.
*/
public interface DomainFile extends Comparable<DomainFile> {
@ -48,8 +48,8 @@ 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 the StoredObj that is associated with the data.
* @return the name
*/
public String getName();
@ -61,7 +61,7 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns a unique file-ID
* @return
* @return the ID
*/
public String getFileID();
@ -79,27 +79,31 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns the path name to the domain object.
* @return the path name
*/
public String getPathname();
/**
* Returns the local storage location for the project that this DomainFile belongs to.
* @return the location
*/
public ProjectLocator getProjectLocator();
/**
* Returns content-type string
* @return the content type
*/
public String getContentType();
/**
* Returns the underlying Class for the domain object in this
* domain file.
* Returns the underlying Class for the domain object in this domain file.
* @return the class
*/
public Class<? extends DomainObject> getDomainObjectClass();
/**
* Get the parent domain folder for this domain file.
* @return the parent
*/
public DomainFolder getParent();
@ -210,21 +214,23 @@ public interface DomainFile extends Comparable<DomainFile> {
public boolean canRecover();
/**
* If the file has an updateable domain object with unsaved changes, generate a
* recovery snapshot.
* @return true if snapshot successful or not needed, false if file is busy
* which prevents snapshot, or snapshot was cancelled.
* @throws IOException
* If the file has an updatable domain object with unsaved changes, generate a recovery
* snapshot.
* @return true if snapshot successful or not needed, false if file is busy which prevents
* snapshot, or snapshot was cancelled.
* @throws IOException if there is an exception saving the snapshot
*/
public boolean takeRecoverySnapshot() throws IOException;
/**
* Returns true if this file is in a writable project.
* @return true if writable
*/
public boolean isInWritableProject();
/**
* Get a long value representing the time when the data was last modified.
* @return the time
*/
public long getLastModifiedTime();
@ -238,39 +244,44 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Returns true if this is a checked-out file.
* @return true if checked-out
*/
public boolean isCheckedOut();
/**
* Returns true if this a checked-out file with exclusive access.
* @return true if checked-out exclusively
*/
public boolean isCheckedOutExclusive();
/**
* Returns true if this is a checked-out file which has been modified
* since it was checked-out.
* Returns true if this is a checked-out file which has been modified since it was checked-out.
* @return true if modified since check-out
*/
public boolean modifiedSinceCheckout();
/**
* Returns true if this file may be checked-out from the associated repository.
* User's with read-only repository access will not have checkout ability.
* @return true if can checkout
*/
public boolean canCheckout();
/**
* Returns true if this file may be checked-in to the associated repository.
* @return true if can check-in
*/
public boolean canCheckin();
/**
* Returns true if this file can be merged with the current versioned file.
* @return true if can merge
*/
public boolean canMerge();
/**
* Returns true if this private file may be added to the
* associated repository.
* Returns true if this private file may be added to the associated repository.
* @return true if can add to the repository
*/
public boolean canAddToRepository();
@ -284,47 +295,53 @@ public interface DomainFile extends Comparable<DomainFile> {
public void setReadOnly(boolean state) throws IOException;
/**
* Returns whether the object is read-only. From a
* framework point of view a read-only object can never be
* changed.
* Returns whether the object is read-only. From a framework point of view a read-only object
* can never be changed.
* @return true if read-only
*/
public boolean isReadOnly();
/**
* Returns true if the versioned filesystem can be used to store
* this files content type.
* Returns true if the versioned filesystem can be used to store this files content type.
* @return true if supports version control
*/
public boolean isVersionControlSupported();
/**
* Return true if this is a versioned database, else false
* @return true if versioned
*/
public boolean isVersioned();
/**
* Returns true if the file is versioned but a private copy also exists.
* @return true if hijacked
*/
public boolean isHijacked();
/**
* Return the latest version
* @return the version
*/
public int getLatestVersion();
/**
* Returns true if this file represents the latest version of the
* associated domain object.
* Returns true if this file represents the latest version of the associated domain object.
* @return true if the latest version
*/
public boolean isLatestVersion();
/**
* Return either the latest version if the file is not checked-out or the version that
* was checked-out or a specific version that was requested.
* @return the version
*/
public int getVersion();
/**
* Returns list of all available versions.
* @return the versions
* @throws IOException if there is an exception getting the history
*/
public Version[] getVersionHistory() throws IOException;
@ -334,8 +351,8 @@ public interface DomainFile extends Comparable<DomainFile> {
* @param keepCheckedOut if true, the file will be initially checked-out
* @param monitor progress monitor
* @throws FileInUseException if this file is in-use.
* @throws IOException thrown if an IO or access error occurs. Also
* thrown if file is not private.
* @throws IOException thrown if an IO or access error occurs. Also thrown if file is not
* private.
* @throws CancelledException if the monitor cancelled the operation
*/
public void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
@ -393,7 +410,7 @@ public interface DomainFile extends Comparable<DomainFile> {
/**
* Forcefully terminate a checkout for the associated versioned file.
* The user must be the owner of the checkout or have admin privilege
* The user must be the owner of the checkout or have administrator privilege
* on the versioned filesystem (i.e., repository).
* @param checkoutId checkout ID
* @throws IOException if an IO or access error occurs
@ -456,7 +473,7 @@ public interface DomainFile extends Comparable<DomainFile> {
* @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation.
*/
DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor)
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor)
throws IOException, CancelledException;
/**
@ -464,47 +481,53 @@ public interface DomainFile extends Comparable<DomainFile> {
* @param version version to copy
* @param destFolder destination parent folder
* @param monitor task monitor
* @return the copied file
* @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation.
*/
DomainFile copyVersionTo(int version, DomainFolder destFolder, TaskMonitor monitor)
public DomainFile copyVersionTo(int version, DomainFolder destFolder, TaskMonitor monitor)
throws IOException, CancelledException;
/**
* Get the list of consumers (Objects) for this domain file.
* @return empty array list if there are no consumers
*/
ArrayList<?> getConsumers();
public List<?> getConsumers();
/**
* Return whether the domain object in this domain file has changed.
* @return true if changed
*/
boolean isChanged();
public boolean isChanged();
/**
* Returns true if there is an open domainObject for this file.
* @return true if open
*/
boolean isOpen();
public boolean isOpen();
/**
* Returns true if the domain object in this domain file exists and has an open transaction.
* @return true if busy
*/
boolean isBusy();
public boolean isBusy();
/**
* Pack domain file into specified file.
* Specified file will be overwritten if it already exists.
* @param file destination file
* @param monitor
* @throws IOException
* @param monitor the task monitor
* @throws IOException if there is an exception packing the file
* @throws CancelledException if monitor cancels operation
*/
public void packFile(File file, TaskMonitor monitor) throws IOException, CancelledException;
/**
* Returns an ordered map containting the metadata that has been associated with the corresponding domain object.
* The map contains key,value pairs and are ordered by their insertion order.
* @return a map containting the metadata that has been associated with the corresponding domain object.
* Returns an ordered map containing the metadata that has been associated with the
* corresponding domain object. The map contains key,value pairs and are ordered by their
* insertion order.
* @return a map containing the metadata that has been associated with the corresponding domain
* object.
*/
public Map<String, String> getMetadata();

View file

@ -24,10 +24,10 @@ import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* <code>DomainFolder</code> provides a storage interface for project
* folders. A <code>DomainFolder</code> is an immutable reference to
* a folder contained within a project. The state of a <code>DomainFolder</code>
* object does not track name/parent changes made to the referenced project folder.
* <code>DomainFolder</code> provides a storage interface for project folders. A
* <code>DomainFolder</code> is an immutable reference to a folder contained within a project. The
* state of a <code>DomainFolder</code> object does not track name/parent changes made to the
* referenced project folder.
*/
public interface DomainFolder extends Comparable<DomainFolder> {
/**
@ -42,41 +42,51 @@ public interface DomainFolder extends Comparable<DomainFolder> {
/**
* Return this folder's name.
* @return the name
*/
public String getName();
/**
* 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)
* @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
* decendents is in-use / checked-out.
* @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.
*/
public DomainFolder setName(String newName) throws InvalidNameException, IOException;
/**
* Returns the local storage location for the project that this DomainFolder belongs to.
* @return the locator
*/
public ProjectLocator getProjectLocator();
/**
* Returns the project data
* @return the project data
*/
public ProjectData getProjectData();
/**
* Returns the path name to the domain object.
* @return the path name
*/
public String getPathname();
/**
* Returns true if this file is in a writable project.
* Returns true if this file is in a writable project.
* @return true if writable
*/
public boolean isInWritableProject();
/**
* Return parent folder or null if this DomainFolder is the root folder.
* @return the parent
*/
public DomainFolder getParent();
@ -149,10 +159,11 @@ public interface DomainFolder extends Comparable<DomainFolder> {
/**
* Create a subfolder of this folder.
* @param folderName sub-folder name
* @return the 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 InvalidNameException if name is an empty string of if it contains characters other
* than alphanumerics.
* @throws IOException if IO or access error occurs
*/
public DomainFolder createFolder(String folderName) throws InvalidNameException, IOException;
@ -169,10 +180,11 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* this affects both private and repository folders and files. If not
* connected, only private folders and files are affected.
* @param newParent new parent folder within the same project
* @return the newly relocated folder (the original DomainFolder object becomes invalid since it is immutable)
* @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 decendents
* @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.
*/
@ -182,6 +194,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
* @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.
@ -191,9 +204,7 @@ public interface DomainFolder extends Comparable<DomainFolder> {
CancelledException;
/**
* Allows the framework to react to a request to make this folder the
* "active" one.
* Allows the framework to react to a request to make this folder the "active" one.
*/
public void setActive();
}

View file

@ -115,5 +115,4 @@ public interface DomainFolderChangeListener {
* @param object domain object which was open for update
*/
public void domainFileObjectClosed(DomainFile file, DomainObject object);
}