Refactored FileSystemListenerList to use more modern concurrent

mechanisms
This commit is contained in:
dragonmacher 2021-08-20 08:49:15 -04:00
parent 888f2635b4
commit f1fd921db7
7 changed files with 231 additions and 331 deletions

View file

@ -19,8 +19,8 @@ import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -29,13 +29,13 @@ import org.junit.*;
import generic.test.TestUtils;
import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FileSystemListenerList;
import ghidra.framework.store.FileSystemEventManager;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.lang.Language;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest {
@ -45,11 +45,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
private LocalFileSystem privateFS;
private ProjectFileManager fileMgr;
private DomainFolder root;
private ArrayList<MyEvent> events = new ArrayList<>();
public ProjectFileManagerTest() {
super();
}
private List<MyEvent> events = new ArrayList<>();
@Before
public void setUp() throws Exception {
@ -107,16 +103,16 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
}
private void flushFileSystemEvents() {
FileSystemListenerList privateListenerList =
(FileSystemListenerList) TestUtils.getInstanceField("listeners", privateFS);
FileSystemListenerList sharedListenerList =
(FileSystemListenerList) TestUtils.getInstanceField("listeners", sharedFS);
FileSystemEventManager privateEventManager =
(FileSystemEventManager) TestUtils.getInstanceField("eventManager", privateFS);
FileSystemEventManager sharedEventManager =
(FileSystemEventManager) TestUtils.getInstanceField("eventManager", sharedFS);
flushTheseEvents(privateListenerList);
flushTheseEvents(sharedListenerList);
flushTheseEvents(privateEventManager);
flushTheseEvents(sharedEventManager);
}
private void flushTheseEvents(FileSystemListenerList listenerList) {
private void flushTheseEvents(FileSystemEventManager eventManager) {
// Events get added synchronously, but processed asynchronously, so we can check to see
// if any have been added by an action we triggered without waiting. Also, we know that
// no more events will get added, since we are the thread (the main thread) doing the
@ -124,14 +120,12 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
//
// If there are queued actions, then we have to kick the handling thread and
// let it finish running.
while (listenerList.isProcessingEvents()) {
// give the event tread some time to send events
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// don't care, we will try again
}
try {
assertTrue(eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS));
}
catch (InterruptedException e) {
failWithException("Interrupted waiting for filesystem events", e);
}
}
@ -180,7 +174,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
Language language = getSLEIGH_X86_LANGUAGE();
Program p = new ProgramDB(name, language, language.getDefaultCompilerSpec(), this);
try {
return folder.createFile(name, p, TaskMonitorAdapter.DUMMY_MONITOR);
return folder.createFile(name, p, TaskMonitor.DUMMY);
}
finally {
p.release(this);
@ -215,7 +209,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
assertNotNull(df2);
assertEquals("file2", df.getName());
df1.addToVersionControl("", false, TaskMonitorAdapter.DUMMY_MONITOR);
df1.addToVersionControl("", false, TaskMonitor.DUMMY);
df = fileMgr.getFileByID(fileID1);
assertNotNull(df1);
@ -262,7 +256,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID();
df1.addToVersionControl("", true, TaskMonitorAdapter.DUMMY_MONITOR);
df1.addToVersionControl("", true, TaskMonitor.DUMMY);
assertEquals(fileID, df1.getFileID());
df1.undoCheckout(true);
@ -282,7 +276,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
// create shared file /a/file1 and keep checked-out
DomainFile df1 = createFile(folder, "file1");
String fileID = df1.getFileID();
df1.addToVersionControl("", true, TaskMonitorAdapter.DUMMY_MONITOR);
df1.addToVersionControl("", true, TaskMonitor.DUMMY);
assertEquals(fileID, df1.getFileID());
// Force Hijack - terminate checkout at versioned file-system

View file

@ -15,8 +15,8 @@
*/
package ghidra.framework.store;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* <code>FileSystemListenerList</code> maintains a list of FileSystemListener's.
@ -24,32 +24,31 @@ import java.util.concurrent.CopyOnWriteArrayList;
* all FileSystemListener's within its list. Employs either a synchronous
* and asynchronous notification mechanism.
*/
public class FileSystemListenerList implements FileSystemListener {
public class FileSystemEventManager implements FileSystemListener {
private List<FileSystemListener> listenerList = new CopyOnWriteArrayList<>();
private List<FileSystemListener> listeners = new CopyOnWriteArrayList<>();
private BlockingQueue<FileSystemEvent> eventQueue = new LinkedBlockingQueue<>();
private List<FileSystemEvent> events =
Collections.synchronizedList(new LinkedList<FileSystemEvent>());
private boolean enableAsynchronousDispatching;
private boolean isEventProcessingThreadWaiting;
private boolean alive = true;
private Object lock = new Object();
private volatile boolean disposed = false;
private Thread thread;
/**
* Construct FileSystemListenerList
* Constructor
* @param enableAsynchronousDispatching if true a separate dispatch thread will be used
* to notify listeners. If false, blocking notification will be performed.
*/
public FileSystemListenerList(boolean enableAsynchronousDispatching) {
this.enableAsynchronousDispatching = enableAsynchronousDispatching;
public FileSystemEventManager(boolean enableAsynchronousDispatching) {
if (enableAsynchronousDispatching) {
thread = new FileSystemEventProcessingThread();
thread.start();
}
}
public void dispose() {
alive = false;
synchronized (lock) {
lock.notify();
disposed = true;
if (thread != null) {
thread.interrupt();
}
}
@ -57,13 +56,8 @@ public class FileSystemListenerList implements FileSystemListener {
* Add a listener to this list.
* @param listener the listener
*/
public synchronized void add(FileSystemListener listener) {
listenerList.add(listener);
if (thread == null && enableAsynchronousDispatching) {
thread = new FileSystemEventProcessingThread();
thread.setName("File System Listener");
thread.start();
}
public void add(FileSystemListener listener) {
listeners.add(listener);
}
/**
@ -71,157 +65,106 @@ public class FileSystemListenerList implements FileSystemListener {
* @param listener the listener
*/
public void remove(FileSystemListener listener) {
listenerList.remove(listener);
}
/**
* Remove all listeners from this list.
*/
public void clear() {
listenerList.clear();
listeners.remove(listener);
}
@Override
public void itemMoved(String parentPath, String name, String newParentPath, String newName) {
if (enableAsynchronousDispatching) {
add(new ItemMovedEvent(parentPath, name, newParentPath, newName));
}
else {
for (FileSystemListener l : listenerList) {
l.itemMoved(parentPath, name, newParentPath, newName);
}
}
handleEvent(new ItemMovedEvent(parentPath, name, newParentPath, newName));
}
@Override
public void itemRenamed(String parentPath, String itemName, String newName) {
if (enableAsynchronousDispatching) {
add(new ItemRenamedEvent(parentPath, itemName, newName));
}
else {
for (FileSystemListener l : listenerList) {
l.itemRenamed(parentPath, itemName, newName);
}
}
handleEvent(new ItemRenamedEvent(parentPath, itemName, newName));
}
@Override
public void itemDeleted(String parentPath, String itemName) {
if (enableAsynchronousDispatching) {
add(new ItemDeletedEvent(parentPath, itemName));
}
else {
for (FileSystemListener l : listenerList) {
l.itemDeleted(parentPath, itemName);
}
}
handleEvent(new ItemDeletedEvent(parentPath, itemName));
}
@Override
public void folderRenamed(String parentPath, String folderName, String newFolderName) {
if (enableAsynchronousDispatching) {
add(new FolderRenamedEvent(parentPath, folderName, newFolderName));
}
else {
for (FileSystemListener l : listenerList) {
l.folderRenamed(parentPath, folderName, newFolderName);
}
}
handleEvent(new FolderRenamedEvent(parentPath, folderName, newFolderName));
}
@Override
public void folderMoved(String parentPath, String folderName, String newParentPath) {
if (enableAsynchronousDispatching) {
add(new FolderMovedEvent(parentPath, folderName, newParentPath));
}
else {
for (FileSystemListener l : listenerList) {
l.folderMoved(parentPath, folderName, newParentPath);
}
}
handleEvent(new FolderMovedEvent(parentPath, folderName, newParentPath));
}
@Override
public void folderDeleted(String parentPath, String folderName) {
if (enableAsynchronousDispatching) {
add(new FolderDeletedEvent(parentPath, folderName));
}
else {
for (FileSystemListener l : listenerList) {
l.folderDeleted(parentPath, folderName);
}
}
handleEvent(new FolderDeletedEvent(parentPath, folderName));
}
@Override
public void itemCreated(String parentPath, String itemName) {
if (enableAsynchronousDispatching) {
add(new ItemCreatedEvent(parentPath, itemName));
}
else {
for (FileSystemListener l : listenerList) {
l.itemCreated(parentPath, itemName);
}
}
handleEvent(new ItemCreatedEvent(parentPath, itemName));
}
@Override
public void folderCreated(String parentPath, String folderName) {
if (enableAsynchronousDispatching) {
add(new FolderCreatedEvent(parentPath, folderName));
}
else {
for (FileSystemListener l : listenerList) {
l.folderCreated(parentPath, folderName);
}
}
handleEvent(new FolderCreatedEvent(parentPath, folderName));
}
@Override
public void itemChanged(String parentPath, String itemName) {
if (enableAsynchronousDispatching) {
add(new ItemChangedEvent(parentPath, itemName));
}
else {
for (FileSystemListener l : listenerList) {
l.itemChanged(parentPath, itemName);
}
}
handleEvent(new ItemChangedEvent(parentPath, itemName));
}
@Override
public void syncronize() {
if (enableAsynchronousDispatching) {
// Note: synchronize calls will only work when using a threaded event queue
if (isAsynchronous()) {
add(new SynchronizeEvent());
}
}
private boolean isAsynchronous() {
return thread != null;
}
private void add(FileSystemEvent ev) {
if (!listenerList.isEmpty()) {
events.add(ev);
synchronized (lock) {
lock.notify();
}
if (!listeners.isEmpty()) {
eventQueue.add(ev);
}
}
private void handleEvent(FileSystemEvent e) {
if (disposed) {
return;
}
if (isAsynchronous()) {
add(e);
}
else {
e.process(listeners);
}
}
/**
* Returns true if this class is processing events <b>or</b> needs to process events that are
* in its event queue.
* Blocks until all current events have been processed.
* <p>
* Note: clients should only use this method when {@link #isAsynchronous()} returns true, since
* this class cannot track when non-threaded events have finished broadcasting to listeners.
* In a synchronous use case, any test that needs to know when client events have been processed
* must use some other mechanism to know when event processing is finished.
*
* @return true if this class is processing events <b>or</b> needs to process events that are
* in its event queue.
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code time} argument
* @return true if the events were processed in the given timeout
* @throws InterruptedException if this waiting thread is interrupted
*/
public boolean isProcessingEvents() {
synchronized (this) {
if (thread == null) {
return false; // non-threaded; does not 'process' events, done synchronously
}
public boolean flushEvents(long timeout, TimeUnit unit) throws InterruptedException {
if (!isAsynchronous()) {
return true; // each thread processes its own event
}
synchronized (lock) { // lock so nobody adds new events
return !isEventProcessingThreadWaiting || (events.size() > 0);
}
MarkerEvent event = new MarkerEvent();
eventQueue.add(event);
return event.waitForEvent(timeout, unit);
}
//==================================================================================================
@ -231,42 +174,25 @@ public class FileSystemListenerList implements FileSystemListener {
private class FileSystemEventProcessingThread extends Thread {
FileSystemEventProcessingThread() {
super("File System Event Processor");
super("File System Listener");
setDaemon(true);
}
@Override
public void run() {
while (alive) {
while (!events.isEmpty()) {
FileSystemEvent event;
synchronized (lock) {
event = events.remove(0);
}
synchronized (FileSystemListenerList.this) {
for (FileSystemListener l : listenerList) {
event.dispatch(l);
}
}
}
doWait();
}
}
while (!disposed) {
private void doWait() {
try {
synchronized (lock) {
if (alive && events.isEmpty()) {
isEventProcessingThreadWaiting = true;
lock.wait();
}
FileSystemEvent event;
try {
event = eventQueue.take();
event.process(listeners);
}
catch (InterruptedException e) {
// interrupt has been cleared; if other threads rely on this interrupted state,
// then mark the thread as interrupted again by calling:
// Thread.currentThread().interrupt();
// For now, this code relies on the 'alive' flag to know when to terminate
}
}
catch (InterruptedException e) {
// not sure why we are ignoring this
}
finally {
isEventProcessingThreadWaiting = false;
}
}
}
@ -284,7 +210,18 @@ public class FileSystemListenerList implements FileSystemListener {
this.newName = newName;
}
void process(List<FileSystemListener> listeners) {
for (FileSystemListener l : listeners) {
dispatch(l);
}
}
abstract void dispatch(FileSystemListener listener);
@Override
public String toString() {
return getClass().getSimpleName();
}
}
private static class ItemMovedEvent extends FileSystemEvent {
@ -396,4 +333,29 @@ public class FileSystemListenerList implements FileSystemListener {
listener.syncronize();
}
}
// an event used by the flush method to mark when current events have been processed
private static class MarkerEvent extends FileSystemEvent {
private CountDownLatch latch = new CountDownLatch(1);
MarkerEvent() {
super(null, null, null, null);
}
@Override
void dispatch(FileSystemListener listener) {
// we don't actually process the event
}
@Override
void process(List<FileSystemListener> listeners) {
latch.countDown();
}
boolean waitForEvent(long timeout, TimeUnit unit) throws InterruptedException {
return latch.await(timeout, unit);
}
}
}

View file

@ -81,7 +81,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
/**
* Constructor.
* @param file path path for root directory.
* @param rootPath path path for root directory.
* @param isVersioned if true item versioning will be enabled.
* @param readOnly if true modifications within this file-system will not be allowed
* and result in an ReadOnlyException
@ -740,7 +740,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
subfolder.name = name;
folder.folders.put(name, subfolder);
if (option == GetFolderOption.CREATE_ALL_NOTIFY) {
listeners.folderCreated(folder.getPathname(), name);
eventManager.folderCreated(folder.getPathname(), name);
}
}
else {
@ -943,7 +943,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
indexJournal.close();
}
listeners.folderCreated(parentPath, getName(path));
eventManager.folderCreated(parentPath, getName(path));
}
/*
@ -978,7 +978,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
indexJournal.close();
}
listeners.folderDeleted(getParentPath(folderPath), getName(folderPath));
eventManager.folderDeleted(getParentPath(folderPath), getName(folderPath));
}
void migrateItem(LocalFolderItem item) throws IOException {
@ -1085,10 +1085,10 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
}
if (folderPath.equals(newFolderPath)) {
listeners.itemRenamed(folderPath, name, newName);
eventManager.itemRenamed(folderPath, name, newName);
}
else {
listeners.itemMoved(folderPath, name, newFolderPath, newName);
eventManager.itemMoved(folderPath, name, newFolderPath, newName);
}
deleteEmptyVersionedFolders(folderPath);
@ -1142,7 +1142,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
updateAffectedItemPaths(folder);
listeners.folderMoved(parentPath, folderName, newParentPath);
eventManager.folderMoved(parentPath, folderName, newParentPath);
deleteEmptyVersionedFolders(parentPath);
}
@ -1198,7 +1198,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
updateAffectedItemPaths(folder);
listeners.folderRenamed(parentPath, folderName, newFolderName);
eventManager.folderRenamed(parentPath, folderName, newFolderName);
}
/*

View file

@ -74,7 +74,7 @@ public abstract class LocalFileSystem implements FileSystem {
protected final File root;
protected final boolean isVersioned;
protected final boolean readOnly;
protected final FileSystemListenerList listeners;
protected final FileSystemEventManager eventManager;
private RepositoryLogger repositoryLogger;
@ -187,7 +187,7 @@ public abstract class LocalFileSystem implements FileSystem {
this.isVersioned = isVersioned;
this.readOnly = readOnly;
listeners = new FileSystemListenerList(enableAsyncronousDispatching);
eventManager = new FileSystemEventManager(enableAsyncronousDispatching);
}
@ -205,7 +205,7 @@ public abstract class LocalFileSystem implements FileSystem {
this.root = null;
this.isVersioned = false;
this.readOnly = true;
listeners = null;
eventManager = null;
}
private void cleanupTemporaryFiles(String folderPath) {
@ -542,7 +542,7 @@ public abstract class LocalFileSystem implements FileSystem {
}
}
listeners.itemCreated(parentPath, name);
eventManager.itemCreated(parentPath, name);
return dataFile;
}
@ -632,10 +632,10 @@ public abstract class LocalFileSystem implements FileSystem {
success = true;
if (folderPath.equals(newFolderPath)) {
listeners.itemRenamed(folderPath, name, newName);
eventManager.itemRenamed(folderPath, name, newName);
}
else {
listeners.itemMoved(folderPath, name, newFolderPath, newName);
eventManager.itemMoved(folderPath, name, newFolderPath, newName);
}
deleteEmptyVersionedFolders(folderPath);
deallocateItemStorage(folderPath, name);
@ -675,8 +675,8 @@ public abstract class LocalFileSystem implements FileSystem {
*/
@Override
public void addFileSystemListener(FileSystemListener listener) {
if (listeners != null) {
listeners.add(listener);
if (eventManager != null) {
eventManager.add(listener);
}
}
@ -685,8 +685,8 @@ public abstract class LocalFileSystem implements FileSystem {
*/
@Override
public void removeFileSystemListener(FileSystemListener listener) {
if (listeners != null) {
listeners.remove(listener);
if (eventManager != null) {
eventManager.remove(listener);
}
}
@ -694,7 +694,7 @@ public abstract class LocalFileSystem implements FileSystem {
* Returns file system listener.
*/
FileSystemListener getListener() {
return listeners;
return eventManager;
}
/**
@ -844,8 +844,8 @@ public abstract class LocalFileSystem implements FileSystem {
@Override
public void dispose() {
if (listeners != null) {
listeners.dispose();
if (eventManager != null) {
eventManager.dispose();
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,14 +15,13 @@
*/
package ghidra.framework.store.local;
import ghidra.framework.store.FolderNotEmptyException;
import ghidra.util.*;
import ghidra.util.exception.DuplicateFileException;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import ghidra.framework.store.FolderNotEmptyException;
import ghidra.util.*;
import ghidra.util.exception.DuplicateFileException;
import utilities.util.FileUtilities;
/**
@ -150,12 +148,12 @@ public class MangledLocalFileSystem extends LocalFileSystem {
if (dirList == null) {
throw new FileNotFoundException("Folder " + folderPath + " not found");
}
ArrayList<String> fileList = new ArrayList<String>(dirList.length);
for (int i = 0; i < dirList.length; i++) {
String name = dirList[i].getName();
if (name.endsWith(PROPERTY_EXT) && dirList[i].isFile()) {
if (!NamingUtilities.isValidMangledName(dirList[i].getName())) {
log.warn("Ignoring property file with bad name: " + dirList[i]);
ArrayList<String> fileList = new ArrayList<>(dirList.length);
for (File element : dirList) {
String name = element.getName();
if (name.endsWith(PROPERTY_EXT) && element.isFile()) {
if (!NamingUtilities.isValidMangledName(element.getName())) {
log.warn("Ignoring property file with bad name: " + element);
continue;
}
int index = name.lastIndexOf(PROPERTY_EXT);
@ -172,6 +170,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
/*
* @see ghidra.framework.store.FileSystem#getFolders(java.lang.String)
*/
@Override
public synchronized String[] getFolderNames(String folderPath) throws IOException {
File dir = getFile(folderPath);
@ -179,12 +178,12 @@ public class MangledLocalFileSystem extends LocalFileSystem {
if (dirList == null) {
throw new FileNotFoundException("Folder " + folderPath + " not found");
}
ArrayList<String> folderList = new ArrayList<String>(dirList.length);
for (int i = 0; i < dirList.length; i++) {
if (!dirList[i].isDirectory()) {
ArrayList<String> folderList = new ArrayList<>(dirList.length);
for (File element : dirList) {
if (!element.isDirectory()) {
continue;
}
String name = demangleName(dirList[i].getName());
String name = demangleName(element.getName());
if (name != null) {
folderList.add(name);
}
@ -196,6 +195,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
/*
* @see ghidra.framework.store.FileSystem#createFolder(java.lang.String, java.lang.String)
*/
@Override
public synchronized void createFolder(String parentPath, String folderName)
throws InvalidNameException, IOException {
@ -218,6 +218,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
/*
* @see ghidra.framework.store.FileSystem#deleteFolder(java.lang.String)
*/
@Override
public synchronized void deleteFolder(String folderPath) throws IOException {
if (readOnly) {
@ -243,12 +244,13 @@ public class MangledLocalFileSystem extends LocalFileSystem {
}
FileUtilities.deleteDir(file);
listeners.folderDeleted(getParentPath(folderPath), getName(folderPath));
eventManager.folderDeleted(getParentPath(folderPath), getName(folderPath));
}
/*
* @see ghidra.framework.store.FileSystem#moveFolder(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public synchronized void moveFolder(String parentPath, String folderName, String newParentPath)
throws InvalidNameException, IOException {
@ -277,7 +279,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
throw new IOException("move failed for unknown reason");
}
listeners.folderMoved(parentPath, folderName, newParentPath);
eventManager.folderMoved(parentPath, folderName, newParentPath);
deleteEmptyVersionedFolders(parentPath);
}
finally {
@ -290,7 +292,9 @@ public class MangledLocalFileSystem extends LocalFileSystem {
/*
* @see ghidra.framework.store.FileSystem#renameFolder(java.lang.String, java.lang.String, java.lang.String)
*/
public synchronized void renameFolder(String parentPath, String folderName, String newFolderName)
@Override
public synchronized void renameFolder(String parentPath, String folderName,
String newFolderName)
throws InvalidNameException, IOException {
if (readOnly) {
@ -315,7 +319,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
throw new IOException("Folder may contain files that are in use");
}
listeners.folderRenamed(parentPath, folderName, newFolderName);
eventManager.folderRenamed(parentPath, folderName, newFolderName);
}
/**
@ -329,7 +333,8 @@ public class MangledLocalFileSystem extends LocalFileSystem {
throw new FileNotFoundException("Empty read-only file system");
}
if (path.charAt(0) != SEPARATOR_CHAR) {
throw new FileNotFoundException("Path names must begin with \'" + SEPARATOR_CHAR + "\'");
throw new FileNotFoundException(
"Path names must begin with \'" + SEPARATOR_CHAR + "\'");
}
if (path.length() == 1) {
return root;
@ -344,9 +349,9 @@ public class MangledLocalFileSystem extends LocalFileSystem {
}
StringBuilder buf = new StringBuilder();
String[] split = path.split(SEPARATOR);
for (int i = 0; i < split.length; i++) {
for (String element : split) {
buf.append(SEPARATOR_CHAR);
buf.append(escapeHiddenDirPrefixChars(split[i]));
buf.append(escapeHiddenDirPrefixChars(element));
}
return NamingUtilities.mangle(buf.toString());
}
@ -399,7 +404,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
String parentPath = getParentPath(folderPath);
createFolders(parentDir, parentPath);
folderDir.mkdir();
listeners.folderCreated(parentPath, getName(folderPath));
eventManager.folderCreated(parentPath, getName(folderPath));
}
/*
@ -443,7 +448,8 @@ public class MangledLocalFileSystem extends LocalFileSystem {
}
IndexedV1LocalFileSystem indexedFs =
new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(), isVersioned, false, false, true);
new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(), isVersioned, false, false,
true);
migrationInProgress = true;
migrateFolder(SEPARATOR, indexedFs);

View file

@ -41,7 +41,7 @@ import ghidra.util.task.TaskMonitor;
public class RemoteFileSystem implements FileSystem, RemoteAdapterListener {
private RepositoryAdapter repository;
private FileSystemListenerList listeners = new FileSystemListenerList(true);
private FileSystemEventManager eventManager = new FileSystemEventManager(true);
/**
* Construct a new remote file system which corresponds to a remote repository.
@ -49,7 +49,7 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener {
*/
public RemoteFileSystem(RepositoryAdapter repository) {
this.repository = repository;
repository.setFileSystemListener(listeners);
repository.setFileSystemListener(eventManager);
repository.addListener(this);
}
@ -65,12 +65,12 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener {
@Override
public void addFileSystemListener(FileSystemListener listener) {
listeners.add(listener);
eventManager.add(listener);
}
@Override
public void removeFileSystemListener(FileSystemListener listener) {
listeners.remove(listener);
eventManager.remove(listener);
}
@Override
@ -230,13 +230,13 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener {
@Override
public void connectionStateChanged(Object adapter) {
if (adapter == repository) {
listeners.syncronize();
eventManager.syncronize();
}
}
@Override
public void dispose() {
listeners.dispose();
eventManager.dispose();
}
}

View file

@ -18,15 +18,14 @@ package ghidra.framework.store.local;
import static org.junit.Assert.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import db.*;
import db.buffers.BufferFile;
import generic.test.AbstractGenericTest;
import generic.test.TestUtils;
import generic.test.*;
import ghidra.framework.store.*;
import ghidra.util.InvalidNameException;
import ghidra.util.PropertyFile;
@ -41,7 +40,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
LocalFileSystem fs;
File projectDir;
ArrayList<MyEvent> events = new ArrayList<>();
List<MyEvent> events = new ArrayList<>();
public AbstractLocalFileSystemTest(boolean useIndexedFileSystem) {
super();
@ -51,7 +50,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
@Before
public void setUp() throws Exception {
File tempDir = new File(AbstractGenericTest.getTestDirectoryPath());
File tempDir = new File(AbstractGTest.getTestDirectoryPath());
projectDir = new File(tempDir, "testproject");
FileUtilities.deleteDir(projectDir);
projectDir.mkdir();
@ -114,6 +113,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
Assert.fail();
}
catch (IOException e) {
// expected
}
try {
@ -121,6 +121,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
Assert.fail();
}
catch (IOException e) {
// expected
}
}
@ -161,78 +162,6 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
checkEvent("Folder Created", "/a1/a2/a3", "a4", null, null, events.get(3));
}
// @Test
// public void testFolderPathTooLong() throws Exception {
//
// StringBuffer sb = new StringBuffer();
// int projectDirLength = projectDir.getAbsolutePath().length();
// int i = 0;
// while (sb.length() + projectDirLength < 248) {
// sb.append("/a" + i);
// ++i;
// }
// try {
// fs.createFolder(sb.toString(), "aaaa");
// Assert.fail("Should have gotten IO exception on too long of a filename!");
// }
// catch (IOException e) {
// }
// }
//
// @Test
// public void testDataFilePathTooLong() throws Exception {
// fs.createFolder("/", "abc");
// String data = "This is a test";
// byte[] dataBytes = data.getBytes();
//
// StringBuffer sb = new StringBuffer();
// int projectDirLength = projectDir.getAbsolutePath().length();
// int i = 0;
// while (sb.length() + projectDirLength < 248) {
// sb.append("/a" + i);
// ++i;
// }
// try {
// fs.createDataFile(sb.toString(), "freddxxxx", new ByteArrayInputStream(dataBytes), null,
// "Data", null);
// Assert.fail("Should have gotten IO Exception!");
// }
// catch (IOException e) {
// }
//
// }
//
// public void testDataBasePathTooLong() throws Exception {
// fs.createFolder("/", "abc");
// DBHandle dbh = new DBHandle();
// long id = dbh.startTransaction();
// dbh.createTable("test", new Schema(0, "key", new Class[] { IntField.class },
// new String[] { "dummy" }));
// dbh.endTransaction(id, true);
// int projectDirLength = projectDir.getAbsolutePath().length();
// StringBuffer sb = new StringBuffer();
// int i = 0;
// while (sb.length() + projectDirLength < 248) {
// sb.append("/a" + i);
// ++i;
// }
// try {
// fs.createDatabase(sb.toString(), "freddxxxx", null, "Database", dbh.getBufferSize(),
// "bob", null);
// Assert.fail("Should have gotten IO Exception!");
// }
// catch (IOException e) {
// }
// }
/**
* @param string
* @param string2
* @param string3
* @param object
* @param object2
* @param object3
*/
private void checkEvent(String op, String path, String name, String newPath, String newName,
Object evObj) {
MyEvent event = (MyEvent) evObj;
@ -262,6 +191,7 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
Assert.fail();
}
catch (FolderNotEmptyException e) {
// expected
}
}
@ -337,12 +267,14 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
Assert.fail();
}
catch (FileNotFoundException e) {
// expected
}
fs.createFolder("/b", "def");
try {
fs.moveFolder("/mno/abc", "def", "/b");
}
catch (DuplicateFileException e) {
// expected
}
}
@ -675,8 +607,6 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
DataFileItem df = fs.createDataFile("/abc", "fred", new ByteArrayInputStream(dataBytes),
null, "Data", null);
DataFileItem df2 = fs.createDataFile("/abc", "bob", new ByteArrayInputStream(dataBytes),
null, "Data", null);
createDatabase("/abc", "greg", "123");
String[] items = fs.getItemNames("/abc");
@ -991,25 +921,43 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
@Override
public void syncronize() {
// not tracked
}
}
private void flushFileSystemEvents() {
FileSystemListenerList listenerList =
(FileSystemListenerList) TestUtils.getInstanceField("listeners", fs);
while (listenerList.isProcessingEvents()) {
// give the event tread some time to send events
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
// don't care, we will try again
}
FileSystemEventManager eventManager =
(FileSystemEventManager) TestUtils.getInstanceField("eventManager", fs);
try {
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
failWithException("Interrupted waiting for filesystem events", e);
}
}
}
class MyEvent {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((newName == null) ? 0 : newName.hashCode());
result = prime * result + ((newParentPath == null) ? 0 : newParentPath.hashCode());
result = prime * result + ((op == null) ? 0 : op.hashCode());
result = prime * result + ((parentPath == null) ? 0 : parentPath.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
MyEvent other = (MyEvent) obj;
return eq(op, other.op) && eq(parentPath, other.parentPath) && eq(name, other.name) &&
eq(newParentPath, other.newParentPath) && eq(newName, other.newName);
}
String op;
String parentPath;
String name;
@ -1024,18 +972,8 @@ class MyEvent {
this.newName = newName;
}
@Override
public boolean equals(Object obj) {
MyEvent other = (MyEvent) obj;
return eq(op, other.op) && eq(parentPath, other.parentPath) && eq(name, other.name) &&
eq(newParentPath, other.newParentPath) && eq(newName, other.newName);
}
private boolean eq(String s1, String s2) {
if (s1 == null) {
return s2 == null;
}
return s1.equals(s2);
return Objects.equals(s1, s2);
}
@Override