mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Refactored FileSystemListenerList to use more modern concurrent
mechanisms
This commit is contained in:
parent
888f2635b4
commit
f1fd921db7
7 changed files with 231 additions and 331 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue