GP-4456 Significantly improved shared project directory performance when directories contain a very large number of files.

This commit is contained in:
ghidra1
2024-03-25 18:56:02 -04:00
parent 79afe5a702
commit ea8357348e
10 changed files with 187 additions and 130 deletions
@@ -74,6 +74,14 @@ public interface FileSystem {
*/ */
public String[] getItemNames(String folderPath) throws IOException; public String[] getItemNames(String folderPath) throws IOException;
/**
* Returns a list of the folder items contained in the given folder.
* @param folderPath the path of the folder.
* @return a list of folder items.
* @throws IOException
*/
public FolderItem[] getItems(String folderPath) throws IOException;
/** /**
* Returns the FolderItem in the given folder with the given name * Returns the FolderItem in the given folder with the given name
* @param folderPath the folder path containing the item. * @param folderPath the folder path containing the item.
@@ -108,8 +116,8 @@ public interface FileSystem {
* all alphanumerics * all alphanumerics
* @throws IOException thrown if an IO error occurs. * @throws IOException thrown if an IO error occurs.
*/ */
public void createFolder(String parentPath, String folderName) throws InvalidNameException, public void createFolder(String parentPath, String folderName)
IOException; throws InvalidNameException, IOException;
/** /**
* Create a new database item within the specified parent folder using the contents * Create a new database item within the specified parent folder using the contents
@@ -133,8 +141,8 @@ public interface FileSystem {
*/ */
public DatabaseItem createDatabase(String parentPath, String name, String fileID, public DatabaseItem createDatabase(String parentPath, String name, String fileID,
BufferFile bufferFile, String comment, String contentType, boolean resetDatabaseId, BufferFile bufferFile, String comment, String contentType, boolean resetDatabaseId,
TaskMonitor monitor, String user) throws InvalidNameException, IOException, TaskMonitor monitor, String user)
CancelledException; throws InvalidNameException, IOException, CancelledException;
/** /**
* Create a new empty database item within the specified parent folder. * Create a new empty database item within the specified parent folder.
@@ -176,8 +184,8 @@ public interface FileSystem {
* @throws CancelledException if cancelled by monitor * @throws CancelledException if cancelled by monitor
*/ */
public DataFileItem createDataFile(String parentPath, String name, InputStream istream, public DataFileItem createDataFile(String parentPath, String name, InputStream istream,
String comment, String contentType, TaskMonitor monitor) throws InvalidNameException, String comment, String contentType, TaskMonitor monitor)
IOException, CancelledException; throws InvalidNameException, IOException, CancelledException;
/** /**
* Creates a new file item from a packed file. * Creates a new file item from a packed file.
@@ -195,8 +203,8 @@ public interface FileSystem {
* @throws CancelledException if cancelled by monitor * @throws CancelledException if cancelled by monitor
*/ */
public FolderItem createFile(String parentPath, String name, File packedFile, public FolderItem createFile(String parentPath, String name, File packedFile,
TaskMonitor monitor, String user) throws InvalidNameException, IOException, TaskMonitor monitor, String user)
CancelledException; throws InvalidNameException, IOException, CancelledException;
/** /**
* Delete the specified folder. * Delete the specified folder.
@@ -60,9 +60,8 @@ public interface FolderItem {
/** /**
* Return the file ID if one has been established or null * Return the file ID if one has been established or null
* @throws IOException thrown if IO or access error occurs
*/ */
String getFileID() throws IOException; String getFileID();
/** /**
* Assign a new file-ID to this local non-versioned file. * Assign a new file-ID to this local non-versioned file.
@@ -288,8 +287,8 @@ public interface FolderItem {
* @throws IOException * @throws IOException
* @throws CancelledException if monitor cancels operation * @throws CancelledException if monitor cancels operation
*/ */
public void output(File outputFile, int version, TaskMonitor monitor) throws IOException, public void output(File outputFile, int version, TaskMonitor monitor)
CancelledException; throws IOException, CancelledException;
/** /**
* Returns this instance after refresh or null if item no longer exists * Returns this instance after refresh or null if item no longer exists
@@ -604,9 +604,8 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
deallocateItemStorage(parentPath, name); deallocateItemStorage(parentPath, name);
} }
finally { finally {
Msg.warn(this, Msg.warn(this, "Detected orphaned project file " + conflictedItemStorageName +
"Detected orphaned project file " + conflictedItemStorageName + ": " + ": " + getPath(parentPath, name));
getPath(parentPath, name));
} }
} }
@@ -893,8 +892,7 @@ public class IndexedLocalFileSystem extends LocalFileSystem {
} }
@Override @Override
protected String[] getItemNames(String folderPath, boolean includeHiddenFiles) public String[] getItemNames(String folderPath, boolean includeHiddenFiles) throws IOException {
throws IOException {
if (readOnly) { if (readOnly) {
refreshReadOnlyIndex(); refreshReadOnlyIndex();
} }
@@ -134,7 +134,8 @@ public class IndexedV1LocalFileSystem extends IndexedLocalFileSystem {
} }
@Override @Override
public FolderItem getItem(String fileID) throws IOException, UnsupportedOperationException { public LocalFolderItem getItem(String fileID)
throws IOException, UnsupportedOperationException {
checkDisposed(); checkDisposed();
if (fileIdMap == null) { if (fileIdMap == null) {
return null; return null;
@@ -366,13 +366,9 @@ public abstract class LocalFileSystem implements FileSystem {
protected abstract void deallocateItemStorage(String folderPath, String itemName) protected abstract void deallocateItemStorage(String folderPath, String itemName)
throws IOException; throws IOException;
protected abstract String[] getItemNames(String folderPath, boolean includeHiddenFiles) public abstract String[] getItemNames(String folderPath, boolean includeHiddenFiles)
throws IOException; throws IOException;
/**
*
* @see ghidra.framework.store.FileSystem#getItemNames(java.lang.String)
*/
@Override @Override
public synchronized String[] getItemNames(String folderPath) throws IOException { public synchronized String[] getItemNames(String folderPath) throws IOException {
return getItemNames(folderPath, false); return getItemNames(folderPath, false);
@@ -407,10 +403,21 @@ public abstract class LocalFileSystem implements FileSystem {
} }
@Override @Override
public FolderItem getItem(String fileID) throws IOException, UnsupportedOperationException { public LocalFolderItem getItem(String fileID)
throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException("getItem by File-ID"); throw new UnsupportedOperationException("getItem by File-ID");
} }
@Override
public LocalFolderItem[] getItems(String folderPath) throws IOException {
String[] itemNames = getItemNames(folderPath, false);
LocalFolderItem[] folderItems = new LocalFolderItem[itemNames.length];
for (int i = 0; i < itemNames.length; i++) {
folderItems[i] = getItem(folderPath, itemNames[i]);
}
return folderItems;
}
@Override @Override
public synchronized LocalDatabaseItem createDatabase(String parentPath, String name, public synchronized LocalDatabaseItem createDatabase(String parentPath, String name,
String fileID, BufferFile bufferFile, String comment, String contentType, String fileID, BufferFile bufferFile, String comment, String contentType,
@@ -729,7 +736,7 @@ public abstract class LocalFileSystem implements FileSystem {
if (folderPath.length() == 1) { if (folderPath.length() == 1) {
return; return;
} }
String[] items = getItemNames(folderPath); String[] items = getItemNames(folderPath, false);
if (items.length > 0) { if (items.length > 0) {
return; return;
} }
@@ -140,8 +140,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
// } // }
@Override @Override
protected String[] getItemNames(String folderPath, boolean includeHiddenFiles) public String[] getItemNames(String folderPath, boolean includeHiddenFiles) throws IOException {
throws IOException {
File dir = getFile(folderPath); File dir = getFile(folderPath);
File[] dirList = dir.listFiles(); File[] dirList = dir.listFiles();
@@ -294,8 +293,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
*/ */
@Override @Override
public synchronized void renameFolder(String parentPath, String folderName, public synchronized void renameFolder(String parentPath, String folderName,
String newFolderName) String newFolderName) throws InvalidNameException, IOException {
throws InvalidNameException, IOException {
if (readOnly) { if (readOnly) {
throw new ReadOnlyException(); throw new ReadOnlyException();
@@ -440,16 +438,14 @@ public class MangledLocalFileSystem extends LocalFileSystem {
cleanupAfterConstruction(); // remove all temporary content cleanupAfterConstruction(); // remove all temporary content
File tmpRoot = File tmpRoot = new File(root.getCanonicalFile().getParentFile(),
new File(root.getCanonicalFile().getParentFile(), HIDDEN_DIR_PREFIX + '.' + HIDDEN_DIR_PREFIX + '.' + root.getName());
root.getName());
if (tmpRoot.exists() || !tmpRoot.mkdir()) { if (tmpRoot.exists() || !tmpRoot.mkdir()) {
throw new IOException("Failed to create data directory: " + tmpRoot); throw new IOException("Failed to create data directory: " + tmpRoot);
} }
IndexedV1LocalFileSystem indexedFs = IndexedV1LocalFileSystem indexedFs = new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(),
new IndexedV1LocalFileSystem(tmpRoot.getAbsolutePath(), isVersioned, false, false, isVersioned, false, false, true);
true);
migrationInProgress = true; migrationInProgress = true;
migrateFolder(SEPARATOR, indexedFs); migrateFolder(SEPARATOR, indexedFs);
@@ -474,7 +470,7 @@ public class MangledLocalFileSystem extends LocalFileSystem {
indexedFs.createFolder(folderPath, name); indexedFs.createFolder(folderPath, name);
migrateFolder(getPath(folderPath, name), indexedFs); migrateFolder(getPath(folderPath, name), indexedFs);
} }
for (String name : getItemNames(folderPath)) { for (String name : getItemNames(folderPath, false)) {
LocalFolderItem item = getItem(folderPath, name); LocalFolderItem item = getItem(folderPath, name);
indexedFs.migrateItem(item); indexedFs.migrateItem(item);
} }
@@ -108,6 +108,19 @@ public class RemoteFileSystem implements FileSystem, RemoteAdapterListener {
return names; return names;
} }
@Override
public FolderItem[] getItems(String folderPath) throws IOException {
RepositoryItem[] items = repository.getItemList(folderPath);
FolderItem[] folderItems = new FolderItem[items.length];
for (int i = 0; i < items.length; i++) {
if (items[i].getItemType() != RepositoryItem.DATABASE) {
throw new IOException("Unsupported file type");
}
folderItems[i] = new RemoteDatabaseItem(repository, items[i]);
}
return folderItems;
}
@Override @Override
public synchronized FolderItem getItem(String folderPath, String name) throws IOException { public synchronized FolderItem getItem(String folderPath, String name) throws IOException {
RepositoryItem item = repository.getItem(folderPath, name); RepositoryItem item = repository.getItem(folderPath, name);
@@ -907,15 +907,17 @@ public class DefaultProjectData implements ProjectData {
private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList, private void findCheckedOutFiles(String folderPath, List<DomainFile> checkoutList,
TaskMonitor monitor) throws IOException, CancelledException { TaskMonitor monitor) throws IOException, CancelledException {
for (String name : fileSystem.getItemNames(folderPath)) { DomainFolder folder = getFolder(folderPath);
if (folder == null) {
return;
}
GhidraFolderData folderData = getRootFolderData().getFolderPathData(folderPath, false);
for (String name : folderData.getFileNames()) {
monitor.checkCancelled(); monitor.checkCancelled();
LocalFolderItem item = fileSystem.getItem(folderPath, name); GhidraFileData fileData = folderData.getFileData(name, false);
if (item.getCheckoutId() != FolderItem.DEFAULT_CHECKOUT_ID) { if (fileData != null && fileData.isCheckedOut()) {
GhidraFolderData folderData = checkoutList.add(new GhidraFile(folderData.getDomainFolder(), name));
getRootFolderData().getFolderPathData(folderPath, false);
if (folderData != null) {
checkoutList.add(new GhidraFile(folderData.getDomainFolder(), name));
}
} }
} }
@@ -113,6 +113,46 @@ public class GhidraFileData {
refresh(); refresh();
} }
/**
* Construct a new file instance with a specified name and a corresponding parent folder using
* up-to-date folder items.
* @param parent parent folder
* @param name file name
* @param folderItem local folder item
* @param versionedFolderItem versioned folder item
*/
GhidraFileData(GhidraFolderData parent, String name, LocalFolderItem folderItem,
FolderItem versionedFolderItem) {
this.parent = parent;
this.name = name;
this.folderItem = folderItem;
this.versionedFolderItem = versionedFolderItem;
this.projectData = parent.getProjectData();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener();
validateCheckout();
updateFileID();
}
void refresh(LocalFolderItem localFolderItem, FolderItem verFolderItem) {
icon = null;
disabledIcon = null;
this.folderItem = localFolderItem;
this.versionedFolderItem = verFolderItem;
validateCheckout();
boolean fileIDset = updateFileID();
if (parent.visited()) {
// NOTE: we should maintain some cached data so we can determine if something really changed
listener.domainFileStatusChanged(getDomainFile(), fileIDset);
}
}
private boolean refresh() throws IOException { private boolean refresh() throws IOException {
String parentPath = parent.getPathname(); String parentPath = parent.getPathname();
if (folderItem == null) { if (folderItem == null) {
@@ -138,9 +178,12 @@ public class GhidraFileData {
if (folderItem == null && versionedFolderItem == null) { if (folderItem == null && versionedFolderItem == null) {
throw new FileNotFoundException(name + " not found"); throw new FileNotFoundException(name + " not found");
} }
return updateFileID();
}
private boolean updateFileID() {
boolean fileIdWasNull = fileID == null; boolean fileIdWasNull = fileID == null;
fileID = folderItem != null ? folderItem.getFileID() : versionedFolderItem.getFileID(); fileID = folderItem != null ? folderItem.getFileID() : versionedFolderItem.getFileID();
return fileIdWasNull && fileID != null; return fileIdWasNull && fileID != null;
} }
@@ -157,26 +200,32 @@ public class GhidraFileData {
disabledIcon = null; disabledIcon = null;
fileIDset |= refresh(); fileIDset |= refresh();
if (parent.visited()) { if (parent.visited()) {
// NOTE: we should maintain some cached data so we can determine if something really changed
listener.domainFileStatusChanged(getDomainFile(), fileIDset); listener.domainFileStatusChanged(getDomainFile(), fileIDset);
} }
} }
private void validateCheckout() throws IOException { private void validateCheckout() {
if (fileSystem.isReadOnly() || !versionedFileSystem.isOnline()) { if (fileSystem.isReadOnly() || !versionedFileSystem.isOnline()) {
return; return;
} }
if (folderItem != null && folderItem.isCheckedOut()) { try {
// Cleanup checkout status which may be stale if (folderItem != null && folderItem.isCheckedOut()) {
if (versionedFolderItem != null) { // Cleanup checkout status which may be stale
ItemCheckoutStatus coStatus = if (versionedFolderItem != null) {
versionedFolderItem.getCheckout(folderItem.getCheckoutId()); ItemCheckoutStatus coStatus =
if (coStatus == null) { versionedFolderItem.getCheckout(folderItem.getCheckoutId());
if (coStatus == null) {
folderItem.clearCheckout();
}
}
else {
folderItem.clearCheckout(); folderItem.clearCheckout();
} }
} }
else { }
folderItem.clearCheckout(); catch (IOException e) {
} // ignore
} }
} }
@@ -24,8 +24,10 @@ import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.protocol.ghidra.TransientProjectData; import ghidra.framework.protocol.ghidra.TransientProjectData;
import ghidra.framework.store.FileSystem; import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FolderItem;
import ghidra.framework.store.FolderNotEmptyException; import ghidra.framework.store.FolderNotEmptyException;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.framework.store.local.LocalFolderItem;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@@ -60,7 +62,7 @@ class GhidraFolderData {
// folderList and fileList are only be used if visited is true // folderList and fileList are only be used if visited is true
private Set<String> folderList = new TreeSet<>(); private Set<String> folderList = new TreeSet<>();
private Set<String> fileList = new TreeSet<>();
private boolean visited; // true if full refresh was performed private boolean visited; // true if full refresh was performed
private Map<String, GhidraFileData> fileDataCache = new HashMap<>(); private Map<String, GhidraFileData> fileDataCache = new HashMap<>();
@@ -172,7 +174,7 @@ class GhidraFolderData {
/** /**
* Get folder data for specified absolute or relative folderPath * Get folder data for specified absolute or relative folderPath
* @param folderPath * @param folderPath absolute or relative folder path
* @param lazy if true folder will not be searched for if not already discovered - in * @param lazy if true folder will not be searched for if not already discovered - in
* this case null will be returned * this case null will be returned
* @return folder data or null if not found or lazy=true and not yet discovered * @return folder data or null if not found or lazy=true and not yet discovered
@@ -236,9 +238,10 @@ class GhidraFolderData {
} }
updateExistenceState(); updateExistenceState();
checkInUse(); checkInUse();
boolean sendEvent = true;
String oldName = name; String oldName = name;
String parentPath = parent.getPathname(); String parentPath = parent.getPathname();
if (folderExists) { if (folderExists) {
fileSystem.renameFolder(parentPath, name, newName); fileSystem.renameFolder(parentPath, name, newName);
} }
@@ -247,8 +250,8 @@ class GhidraFolderData {
versionedFileSystem.renameFolder(parentPath, name, newName); versionedFileSystem.renameFolder(parentPath, name, newName);
} }
catch (IOException e) { catch (IOException e) {
sendEvent = false;
if (folderExists) { if (folderExists) {
// revert local folder name
fileSystem.renameFolder(parentPath, newName, name); fileSystem.renameFolder(parentPath, newName, name);
} }
throw e; throw e;
@@ -260,18 +263,14 @@ class GhidraFolderData {
name = newName; name = newName;
parent.folderDataCache.put(newName, this); parent.folderDataCache.put(newName, this);
fileDataCache.clear();
folderDataCache.clear();
GhidraFolder newFolder = getDomainFolder(); GhidraFolder newFolder = getDomainFolder();
if (parent.visited) { if (parent.visited) {
parent.folderList.remove(oldName); parent.folderList.remove(oldName);
parent.folderList.add(newName); parent.folderList.add(newName);
if (sendEvent) { listener.domainFolderRenamed(newFolder, oldName);
listener.domainFolderRenamed(newFolder, oldName);
}
} }
return newFolder; return newFolder;
} }
} }
@@ -322,7 +321,7 @@ class GhidraFolderData {
boolean isEmpty() { boolean isEmpty() {
try { try {
refresh(false, false, null); // visited will be true upon return refresh(false, false, null); // visited will be true upon return
return folderList.isEmpty() && fileList.isEmpty(); return folderList.isEmpty() && fileDataCache.isEmpty();
} }
catch (IOException e) { catch (IOException e) {
// TODO: what should we return if folder not found or error occurs? // TODO: what should we return if folder not found or error occurs?
@@ -343,7 +342,7 @@ class GhidraFolderData {
Msg.error(this, "Folder refresh failed: " + e.getMessage()); Msg.error(this, "Folder refresh failed: " + e.getMessage());
return new ArrayList<>(); return new ArrayList<>();
} }
return new ArrayList<>(fileList); return new ArrayList<>(fileDataCache.keySet());
} }
/** /**
@@ -375,12 +374,6 @@ class GhidraFolderData {
!newFileName.equals(fileData.getName())) { !newFileName.equals(fileData.getName())) {
throw new AssertException(); throw new AssertException();
} }
if (visited) {
fileList.remove(oldFileName);
}
if (visited) {
fileList.add(newFileName);
}
fileDataCache.put(newFileName, fileData); fileDataCache.put(newFileName, fileData);
if (visited) { if (visited) {
listener.domainFileRenamed(getDomainFile(newFileName), oldFileName); listener.domainFileRenamed(getDomainFile(newFileName), oldFileName);
@@ -404,12 +397,6 @@ class GhidraFolderData {
!newFileName.equals(fileData.getName())) { !newFileName.equals(fileData.getName())) {
throw new AssertException(); throw new AssertException();
} }
if (visited) {
fileList.remove(oldFileName);
}
if (newParent.visited) {
newParent.fileList.add(newFileName);
}
newParent.fileDataCache.put(newFileName, fileData); newParent.fileDataCache.put(newFileName, fileData);
} }
if (visited || newParent.visited) { if (visited || newParent.visited) {
@@ -436,7 +423,6 @@ class GhidraFolderData {
fileData.dispose(); fileData.dispose();
fileDataCache.remove(fileName); fileDataCache.remove(fileName);
if (visited) { if (visited) {
fileList.remove(fileName);
listener.domainFileRemoved(getDomainFolder(), fileName, fileID); listener.domainFileRemoved(getDomainFolder(), fileName, fileID);
} }
} }
@@ -445,21 +431,13 @@ class GhidraFolderData {
if (visited) { if (visited) {
try { try {
fileData = addFileData(fileName); fileData = addFileData(fileName);
if (fileData != null) {
listener.domainFileAdded(fileData.getDomainFile());
}
} }
catch (IOException e) { catch (IOException e) {
// ignore // ignore
} }
if (fileData == null) {
if (fileList.remove(fileName)) {
listener.domainFileRemoved(getDomainFolder(), fileName, null);
}
}
else if (fileList.add(fileName)) {
listener.domainFileAdded(fileData.getDomainFile());
}
else {
listener.domainFileStatusChanged(fileData.getDomainFile(), false);
}
} }
} }
} }
@@ -532,7 +510,6 @@ class GhidraFolderData {
void dispose() { void dispose() {
visited = false; visited = false;
folderList.clear(); folderList.clear();
fileList.clear();
for (GhidraFolderData folderData : folderDataCache.values()) { for (GhidraFolderData folderData : folderDataCache.values()) {
folderData.dispose(); folderData.dispose();
} }
@@ -636,17 +613,28 @@ class GhidraFolderData {
} }
} }
private <T extends FolderItem> Map<String, T> itemMapOf(T[] items) {
Map<String, T> map = new HashMap<>();
for (T item : items) {
if (item != null) {
map.put(item.getName(), item);
}
}
return map;
}
private void refreshFiles(TaskMonitor monitor) throws IOException { private void refreshFiles(TaskMonitor monitor) throws IOException {
String path = getPathname(); String path = getPathname();
boolean hadError = false; Map<String, LocalFolderItem> localItemMap = Map.of();
Map<String, FolderItem> versionedItemMap = Map.of();
HashSet<String> newSet = new HashSet<>(); HashSet<String> newSet = new HashSet<>();
if (folderExists) { if (folderExists) {
try { try {
String[] items = fileSystem.getItemNames(path); localItemMap = itemMapOf(fileSystem.getItems(path));
newSet.addAll(Arrays.asList(items)); newSet.addAll(localItemMap.keySet());
} }
catch (IOException e) { catch (IOException e) {
if (parent != null) { if (parent != null) {
@@ -657,8 +645,8 @@ class GhidraFolderData {
} }
if (versionedFolderExists) { if (versionedFolderExists) {
try { try {
String[] items = versionedFileSystem.getItemNames(path); versionedItemMap = itemMapOf(versionedFileSystem.getItems(path));
newSet.addAll(Arrays.asList(items)); newSet.addAll(versionedItemMap.keySet());
} }
catch (Exception e) { catch (Exception e) {
Msg.error(this, "versioned folder refresh failed: " + e.getMessage()); Msg.error(this, "versioned folder refresh failed: " + e.getMessage());
@@ -667,7 +655,7 @@ class GhidraFolderData {
} }
HashSet<String> oldSet = new HashSet<>(); HashSet<String> oldSet = new HashSet<>();
for (String file : fileList) { for (String file : fileDataCache.keySet()) {
oldSet.add(file); oldSet.add(file);
} }
HashSet<String> oldSetClone = new HashSet<>(oldSet); HashSet<String> oldSetClone = new HashSet<>(oldSet);
@@ -679,23 +667,15 @@ class GhidraFolderData {
} }
// refresh existing // refresh existing
for (String fileName : fileList.toArray(new String[fileList.size()])) { for (GhidraFileData fileData : fileDataCache.values()) {
GhidraFileData fileData = fileDataCache.get(fileName); String fileName = fileData.getName();
if (fileData != null) { LocalFolderItem localFolderItem = localItemMap.get(fileName);
try { FolderItem versionedFolderItem = versionedItemMap.get(fileName);
fileData.statusChanged(); if (localFolderItem == null && versionedFolderItem == null) {
} fileRemoved(fileName);
catch (IOException e) { }
if (!(e instanceof FileNotFoundException)) { else {
if (hadError) { fileData.refresh(localItemMap.get(fileName), versionedItemMap.get(fileName));
throw e;
}
hadError = true; // tolerate single file error and remove file reference
Msg.error(this,
"Domain File error on " + fileData.getPathname() + ": " + e.toString());
}
fileRemoved(fileName);
}
} }
} }
@@ -705,12 +685,12 @@ class GhidraFolderData {
if (monitor != null && monitor.isCancelled()) { if (monitor != null && monitor.isCancelled()) {
break; break;
} }
GhidraFileData fileData = addFileData(fileName); LocalFolderItem localFolderItem = localItemMap.get(fileName);
if (fileData != null) { FolderItem versionedFolderItem = versionedItemMap.get(fileName);
fileList.add(fileName);
if (visited) { GhidraFileData fileData = addFileData(fileName, localFolderItem, versionedFolderItem);
listener.domainFileAdded(fileData.getDomainFile()); if (visited) {
} listener.domainFileAdded(fileData.getDomainFile());
} }
} }
} }
@@ -722,7 +702,6 @@ class GhidraFolderData {
fileID = fileData.getFileID(); fileID = fileData.getFileID();
fileData.dispose(); fileData.dispose();
} }
fileList.remove(filename);
if (visited) { if (visited) {
listener.domainFileRemoved(getDomainFolder(), filename, fileID); listener.domainFileRemoved(getDomainFolder(), filename, fileID);
} }
@@ -860,7 +839,7 @@ class GhidraFolderData {
return true; return true;
} }
if (visited) { if (visited) {
return fileList.contains(fileName); return false;
} }
return addFileData(fileName) != null; return addFileData(fileName) != null;
} }
@@ -888,6 +867,15 @@ class GhidraFolderData {
return fileData; return fileData;
} }
private GhidraFileData addFileData(String fileName, LocalFolderItem folderItem,
FolderItem versionedFolderItem) {
GhidraFileData fileData =
new GhidraFileData(this, fileName, folderItem, versionedFolderItem);
fileDataCache.put(fileName, fileData);
projectData.updateFileIndex(fileData);
return fileData;
}
/** /**
* Get file data for child specified by fileName * Get file data for child specified by fileName
* @param fileName name of file * @param fileName name of file
@@ -1101,7 +1089,7 @@ class GhidraFolderData {
if (fileSystem.getFolderNames(path).length != 0) { if (fileSystem.getFolderNames(path).length != 0) {
return; return;
} }
if (fileSystem.getItemNames(path).length != 0) { if (fileSystem.getItemNames(path, false).length != 0) {
return; return;
} }
delete(); delete();
@@ -1134,7 +1122,6 @@ class GhidraFolderData {
throw new IllegalArgumentException("newParent must differ from current parent"); throw new IllegalArgumentException("newParent must differ from current parent");
} }
checkInUse(); checkInUse();
boolean sendEvent = true;
updateExistenceState(); updateExistenceState();
try { try {
@@ -1152,8 +1139,8 @@ class GhidraFolderData {
newParent.getPathname()); newParent.getPathname());
} }
catch (IOException e) { catch (IOException e) {
sendEvent = false;
if (folderExists) { if (folderExists) {
// revert local folder move
fileSystem.moveFolder(newParent.getPathname(), name, fileSystem.moveFolder(newParent.getPathname(), name,
parent.getPathname()); parent.getPathname());
} }
@@ -1168,9 +1155,6 @@ class GhidraFolderData {
} }
parent.folderDataCache.remove(name); parent.folderDataCache.remove(name);
fileDataCache.clear();
folderDataCache.clear();
if (newParent.visited) { if (newParent.visited) {
newParent.folderList.add(name); newParent.folderList.add(name);
} }
@@ -1179,7 +1163,7 @@ class GhidraFolderData {
parent = newParent; parent = newParent;
GhidraFolder newFolder = getDomainFolder(); GhidraFolder newFolder = getDomainFolder();
if (sendEvent && (parent.visited || newParent.visited)) { if (parent.visited || newParent.visited) {
listener.domainFolderMoved(newFolder, oldParent); listener.domainFolderMoved(newFolder, oldParent);
} }