package org.jabref.gui.importer;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jabref.gui.FindUnlinkedFilesDialog.CheckableTreeNode;
import org.jabref.gui.FindUnlinkedFilesDialog.FileNodeWrapper;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseContext;
/**
* Util class for searching files on the file system which are not linked to a provided {@link BibDatabase}.
*/
public class UnlinkedFilesCrawler {
/**
* File filter, that accepts directories only.
*/
private static final FileFilter DIRECTORY_FILTER = pathname -> (pathname != null) && pathname.isDirectory();
private final BibDatabaseContext databaseContext;
public UnlinkedFilesCrawler(BibDatabaseContext databaseContext) {
this.databaseContext = databaseContext;
}
public CheckableTreeNode searchDirectory(File directory, FileFilter filter) {
UnlinkedPDFFileFilter ff = new UnlinkedPDFFileFilter(filter, databaseContext);
return searchDirectory(directory, ff, new AtomicBoolean(true), null);
}
/**
* Searches recursively all files in the specified directory. <br>
* <br>
* All {@link File}s, which match the {@link FileFilter} that comes with the
* {@link EntryFromFileCreatorManager}, are taken into the resulting tree. <br>
* <br>
* The result will be a tree structure of nodes of the type
* {@link CheckableTreeNode}. <br>
* <br>
* The user objects that are attached to the nodes is the
* {@link FileNodeWrapper}, which wraps the {@link File}-Object. <br>
* <br>
* For ensuring the capability to cancel the work of this recursive method,
* the first position in the integer array 'state' must be set to 1, to keep
* the recursion running. When the states value changes, the method will
* resolve its recursion and return what it has saved so far.
*/
public CheckableTreeNode searchDirectory(File directory, UnlinkedPDFFileFilter ff, AtomicBoolean state, ChangeListener changeListener) {
/* Cancellation of the search from outside! */
if ((state == null) || !state.get()) {
return null;
}
// Return null if the directory is not valid.
if ((directory == null) || !directory.exists() || !directory.isDirectory()) {
return null;
}
File[] filesArray = directory.listFiles(ff);
List<File> files;
if (filesArray == null) {
files = Collections.emptyList();
} else {
files = Arrays.asList(filesArray);
}
CheckableTreeNode root = new CheckableTreeNode(null);
int filesCount = 0;
filesArray = directory.listFiles(DIRECTORY_FILTER);
List<File> subDirectories;
if (filesArray == null) {
subDirectories = Collections.emptyList();
} else {
subDirectories = Arrays.asList(filesArray);
}
for (File subDirectory : subDirectories) {
CheckableTreeNode subRoot = searchDirectory(subDirectory, ff, state, changeListener);
if ((subRoot != null) && (subRoot.getChildCount() > 0)) {
filesCount += ((FileNodeWrapper) subRoot.getUserObject()).fileCount;
root.add(subRoot);
}
}
root.setUserObject(new FileNodeWrapper(directory, files.size() + filesCount));
for (File file : files) {
root.add(new CheckableTreeNode(new FileNodeWrapper(file)));
if (changeListener != null) {
changeListener.stateChanged(new ChangeEvent(this));
}
}
return root;
}
}