search improved

This commit is contained in:
rdavidek 2026-01-27 22:47:22 +01:00
parent 24a0ab7451
commit 0867fa0dab
2 changed files with 114 additions and 47 deletions

View File

@ -77,10 +77,8 @@ public class FileOperations {
while (true) {
try {
Path sourcePath = source.toPath();
System.out.println("[DEBUG] Processing item: " + sourcePath + " (isLink: " + Files.isSymbolicLink(sourcePath) + ")");
if (Files.isSymbolicLink(sourcePath)) {
SymlinkResponse sRes = getSymlinkResponse(source, callback, globalSymlinkResponse);
System.out.println("[DEBUG] Symlink response for " + source.getName() + ": " + sRes);
if (sRes == SymlinkResponse.CANCEL) {
return;
}
@ -91,33 +89,27 @@ public class FileOperations {
// If FOLLOW, sRes will be FOLLOW or FOLLOW_ALL
if (Files.exists(sourcePath)) {
if (Files.isDirectory(sourcePath)) {
System.out.println("[DEBUG] Following symlink as directory: " + sourcePath);
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
// Count the symlink itself as processed to match countItems prediction
currentItem[0]++;
} else {
System.out.println("[DEBUG] Following symlink as file: " + sourcePath);
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
}
} else {
// Link is broken, follow is impossible, copy as link
System.out.println("[DEBUG] Broken link encountered, copying as link: " + sourcePath);
copySymlink(sourcePath, target.toPath(), totalItems, currentItem, callback);
}
break;
} else if (source.isDirectory()) {
System.out.println("[DEBUG] Copying directory: " + sourcePath);
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
// Directory itself counted in countItems but copyDirectory skips increment for root.
// So we increment here.
currentItem[0]++;
} else {
System.out.println("[DEBUG] Copying file: " + sourcePath);
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
}
break;
} catch (IOException e) {
System.out.println("[DEBUG] Error processing " + source.getName() + ": " + e.getMessage());
if (callback != null) {
ErrorResponse res = callback.onError(source, e);
if (res == ErrorResponse.ABORT) throw e;
@ -253,7 +245,6 @@ public class FileOperations {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (callback != null && callback.isCancelled()) return FileVisitResult.TERMINATE;
Path targetFile = finalTarget.resolve(effectiveSource.relativize(file.toAbsolutePath().normalize()));
System.out.println("[DEBUG] Recursive visit (file): " + file + " (isLink: " + attrs.isSymbolicLink() + ")");
if (Files.exists(targetFile, LinkOption.NOFOLLOW_LINKS)) {
if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) return FileVisitResult.CONTINUE;
@ -648,8 +639,28 @@ public class FileOperations {
}
private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, boolean recursive, boolean searchArchives, SearchCallback callback) throws IOException {
if (callback != null && callback.isCancelled()) return;
// Use absolute path for reliable prefix checking
Path absolutePath = directory.toAbsolutePath().normalize();
String pathStr = absolutePath.toString();
callback.onProgress(pathStr);
// Skip troublesome virtual filesystems and device nodes
if (pathStr.startsWith("/proc") || pathStr.startsWith("/sys") || pathStr.startsWith("/dev")) {
return;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path entry : stream) {
if (callback != null && callback.isCancelled()) return;
try {
// Always skip symbolic links during search to prevent infinite loops and hangs on special files
if (Files.isSymbolicLink(entry)) {
continue;
}
if (Files.isDirectory(entry)) {
if (recursive) {
searchRecursive(entry, patternLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
@ -675,9 +686,12 @@ public class FileOperations {
searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, callback);
}
}
} catch (AccessDeniedException e) {
// Silently skip
}
}
} catch (AccessDeniedException e) {
// Ignore directories without access
// Silently skip
}
}
@ -699,7 +713,7 @@ public class FileOperations {
}
}
} catch (IOException ex) {
// Skip files that cannot be read as text
// Silently skip
}
return false;
}
@ -783,12 +797,14 @@ public class FileOperations {
}
private static void searchInArchiveCombined(File archive, String patternLower, Pattern filenameRegex, Pattern contentPattern, SearchCallback callback) {
if (callback != null && callback.isCancelled()) return;
String name = archive.getName().toLowerCase();
try {
if (name.endsWith(".zip") || name.endsWith(".jar")) {
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archive))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (callback.isCancelled()) return;
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
@ -812,6 +828,7 @@ public class FileOperations {
try (org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(is)) {
org.apache.commons.compress.archivers.tar.TarArchiveEntry entry;
while ((entry = tais.getNextTarEntry()) != null) {
if (callback.isCancelled()) return;
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
@ -831,6 +848,7 @@ public class FileOperations {
try (org.apache.commons.compress.archivers.sevenz.SevenZFile sevenZFile = new org.apache.commons.compress.archivers.sevenz.SevenZFile(archive)) {
org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry;
while ((entry = sevenZFile.getNextEntry()) != null) {
if (callback.isCancelled()) return;
if (!entry.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
@ -858,6 +876,7 @@ public class FileOperations {
} else if (name.endsWith(".rar")) {
try (com.github.junrar.Archive rar = new com.github.junrar.Archive(archive)) {
for (com.github.junrar.rarfile.FileHeader fh : rar.getFileHeaders()) {
if (callback.isCancelled()) return;
if (!fh.isDirectory()) {
boolean nameMatched = true;
if (patternLower != null && !patternLower.isEmpty()) {
@ -876,7 +895,9 @@ public class FileOperations {
}
}
}
} catch (Exception ignore) {}
} catch (Exception e) {
// Silently skip archive errors during search
}
}
@ -1063,5 +1084,7 @@ public class FileOperations {
*/
public interface SearchCallback {
void onFileFound(File file, String virtualPath);
default boolean isCancelled() { return false; }
default void onProgress(String status) {}
}
}

View File

@ -26,6 +26,7 @@ public class SearchDialog extends JDialog {
private JTable resultsTable;
private ResultsTableModel tableModel;
private JButton searchButton;
private JButton stopButton;
private JButton cancelButton;
private JButton viewButton;
private JButton editButton;
@ -263,6 +264,14 @@ public class SearchDialog extends JDialog {
searchButton = new JButton("Search");
searchButton.addActionListener(e -> performSearch());
stopButton = new JButton("Cancel");
stopButton.setEnabled(false);
stopButton.addActionListener(e -> {
searching = false;
stopButton.setEnabled(false);
statusLabel.setText("Cancelling...");
});
viewButton = new JButton("View");
viewButton.setEnabled(false);
viewButton.addActionListener(e -> viewSelectedFile());
@ -272,12 +281,16 @@ public class SearchDialog extends JDialog {
editButton.addActionListener(e -> editSelectedFile());
cancelButton = new JButton("Close");
cancelButton.addActionListener(e -> dispose());
cancelButton.addActionListener(e -> {
searching = false;
dispose();
});
JButton openButton = new JButton("Open location");
openButton.addActionListener(e -> openSelectedFile());
buttonPanel.add(searchButton);
buttonPanel.add(stopButton);
buttonPanel.add(viewButton);
buttonPanel.add(editButton);
buttonPanel.add(openButton);
@ -494,6 +507,7 @@ public class SearchDialog extends JDialog {
tableModel.clear();
searchButton.setEnabled(false);
stopButton.setEnabled(true);
searching = true;
// Persist the chosen patterns into history
@ -544,11 +558,25 @@ public class SearchDialog extends JDialog {
// Spustit vyhledávání v samostatném vlákně
SwingWorker<Void, Object[]> worker = new SwingWorker<Void, Object[]>() {
private String currentSearchPath = "";
@Override
protected Void doInBackground() throws Exception {
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, (file, virtualPath) -> {
if (!searching) return;
publish(new Object[]{file, virtualPath});
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, new FileOperations.SearchCallback() {
@Override
public void onFileFound(File file, String virtualPath) {
publish(new Object[]{"file", file, virtualPath});
}
@Override
public boolean isCancelled() {
return !searching;
}
@Override
public void onProgress(String status) {
publish(new Object[]{"progress", status});
}
});
return null;
}
@ -556,25 +584,41 @@ public class SearchDialog extends JDialog {
@Override
protected void process(List<Object[]> chunks) {
for (Object[] chunk : chunks) {
File file = (File) chunk[0];
String virtualPath = (String) chunk[1];
String type = (String) chunk[0];
if ("file".equals(type)) {
File file = (File) chunk[1];
String virtualPath = (String) chunk[2];
tableModel.addResult(new FileItem(file, virtualPath));
// update found count and status
foundCount++;
statusLabel.setText("Found " + foundCount + " — searching...");
// If this is the first found file, select it and focus the table
updateStatusLabel(currentSearchPath);
if (foundCount == 1) {
try {
resultsTable.setRowSelectionInterval(0, 0);
resultsTable.requestFocusInWindow();
} catch (Exception ignore) {}
}
} else if ("progress".equals(type)) {
currentSearchPath = (String) chunk[1];
updateStatusLabel(currentSearchPath);
}
}
}
private void updateStatusLabel(String currentPath) {
String text = "Found " + foundCount;
if (currentPath != null && !currentPath.isEmpty()) {
text += "" + currentPath;
} else {
text += " — searching...";
}
statusLabel.setText(text);
statusLabel.setToolTipText(currentPath);
}
@Override
protected void done() {
searchButton.setEnabled(true);
stopButton.setEnabled(false);
searching = false;
// finalize status
statusProgressBar.setIndeterminate(false);