search improved
This commit is contained in:
parent
24a0ab7451
commit
0867fa0dab
@ -77,10 +77,8 @@ public class FileOperations {
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
Path sourcePath = source.toPath();
|
Path sourcePath = source.toPath();
|
||||||
System.out.println("[DEBUG] Processing item: " + sourcePath + " (isLink: " + Files.isSymbolicLink(sourcePath) + ")");
|
|
||||||
if (Files.isSymbolicLink(sourcePath)) {
|
if (Files.isSymbolicLink(sourcePath)) {
|
||||||
SymlinkResponse sRes = getSymlinkResponse(source, callback, globalSymlinkResponse);
|
SymlinkResponse sRes = getSymlinkResponse(source, callback, globalSymlinkResponse);
|
||||||
System.out.println("[DEBUG] Symlink response for " + source.getName() + ": " + sRes);
|
|
||||||
if (sRes == SymlinkResponse.CANCEL) {
|
if (sRes == SymlinkResponse.CANCEL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -91,33 +89,27 @@ public class FileOperations {
|
|||||||
// If FOLLOW, sRes will be FOLLOW or FOLLOW_ALL
|
// If FOLLOW, sRes will be FOLLOW or FOLLOW_ALL
|
||||||
if (Files.exists(sourcePath)) {
|
if (Files.exists(sourcePath)) {
|
||||||
if (Files.isDirectory(sourcePath)) {
|
if (Files.isDirectory(sourcePath)) {
|
||||||
System.out.println("[DEBUG] Following symlink as directory: " + sourcePath);
|
|
||||||
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
|
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
|
||||||
// Count the symlink itself as processed to match countItems prediction
|
// Count the symlink itself as processed to match countItems prediction
|
||||||
currentItem[0]++;
|
currentItem[0]++;
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[DEBUG] Following symlink as file: " + sourcePath);
|
|
||||||
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
|
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Link is broken, follow is impossible, copy as link
|
// 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);
|
copySymlink(sourcePath, target.toPath(), totalItems, currentItem, callback);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if (source.isDirectory()) {
|
} else if (source.isDirectory()) {
|
||||||
System.out.println("[DEBUG] Copying directory: " + sourcePath);
|
|
||||||
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
|
copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null);
|
||||||
// Directory itself counted in countItems but copyDirectory skips increment for root.
|
// Directory itself counted in countItems but copyDirectory skips increment for root.
|
||||||
// So we increment here.
|
// So we increment here.
|
||||||
currentItem[0]++;
|
currentItem[0]++;
|
||||||
} else {
|
} else {
|
||||||
System.out.println("[DEBUG] Copying file: " + sourcePath);
|
|
||||||
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
|
copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.out.println("[DEBUG] Error processing " + source.getName() + ": " + e.getMessage());
|
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
ErrorResponse res = callback.onError(source, e);
|
ErrorResponse res = callback.onError(source, e);
|
||||||
if (res == ErrorResponse.ABORT) throw e;
|
if (res == ErrorResponse.ABORT) throw e;
|
||||||
@ -253,7 +245,6 @@ public class FileOperations {
|
|||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
if (callback != null && callback.isCancelled()) return FileVisitResult.TERMINATE;
|
if (callback != null && callback.isCancelled()) return FileVisitResult.TERMINATE;
|
||||||
Path targetFile = finalTarget.resolve(effectiveSource.relativize(file.toAbsolutePath().normalize()));
|
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 (Files.exists(targetFile, LinkOption.NOFOLLOW_LINKS)) {
|
||||||
if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) return FileVisitResult.CONTINUE;
|
if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) return FileVisitResult.CONTINUE;
|
||||||
@ -648,36 +639,59 @@ public class FileOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, boolean recursive, boolean searchArchives, SearchCallback callback) throws IOException {
|
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)) {
|
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||||
for (Path entry : stream) {
|
for (Path entry : stream) {
|
||||||
if (Files.isDirectory(entry)) {
|
if (callback != null && callback.isCancelled()) return;
|
||||||
if (recursive) {
|
try {
|
||||||
searchRecursive(entry, patternLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
|
// Always skip symbolic links during search to prevent infinite loops and hangs on special files
|
||||||
}
|
if (Files.isSymbolicLink(entry)) {
|
||||||
} else {
|
continue;
|
||||||
File file = entry.toFile();
|
|
||||||
boolean nameMatched = true;
|
|
||||||
if (patternLower != null && !patternLower.isEmpty()) {
|
|
||||||
nameMatched = matchName(file.getName(), patternLower, filenameRegex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean contentMatched = true;
|
if (Files.isDirectory(entry)) {
|
||||||
if (nameMatched && contentPattern != null) {
|
if (recursive) {
|
||||||
contentMatched = fileMatchesContent(entry, contentPattern);
|
searchRecursive(entry, patternLower, filenameRegex, contentPattern, recursive, searchArchives, callback);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
File file = entry.toFile();
|
||||||
|
boolean nameMatched = true;
|
||||||
|
if (patternLower != null && !patternLower.isEmpty()) {
|
||||||
|
nameMatched = matchName(file.getName(), patternLower, filenameRegex);
|
||||||
|
}
|
||||||
|
|
||||||
if (nameMatched && contentMatched) {
|
boolean contentMatched = true;
|
||||||
callback.onFileFound(file, null);
|
if (nameMatched && contentPattern != null) {
|
||||||
}
|
contentMatched = fileMatchesContent(entry, contentPattern);
|
||||||
|
}
|
||||||
|
|
||||||
// SEARCH IN ARCHIVES
|
if (nameMatched && contentMatched) {
|
||||||
if (searchArchives && isArchiveFile(file)) {
|
callback.onFileFound(file, null);
|
||||||
searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, callback);
|
}
|
||||||
|
|
||||||
|
// SEARCH IN ARCHIVES
|
||||||
|
if (searchArchives && isArchiveFile(file)) {
|
||||||
|
searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (AccessDeniedException e) {
|
||||||
|
// Silently skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (AccessDeniedException e) {
|
} catch (AccessDeniedException e) {
|
||||||
// Ignore directories without access
|
// Silently skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,7 +713,7 @@ public class FileOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// Skip files that cannot be read as text
|
// Silently skip
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -783,12 +797,14 @@ public class FileOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void searchInArchiveCombined(File archive, String patternLower, Pattern filenameRegex, Pattern contentPattern, SearchCallback callback) {
|
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();
|
String name = archive.getName().toLowerCase();
|
||||||
try {
|
try {
|
||||||
if (name.endsWith(".zip") || name.endsWith(".jar")) {
|
if (name.endsWith(".zip") || name.endsWith(".jar")) {
|
||||||
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archive))) {
|
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archive))) {
|
||||||
ZipEntry entry;
|
ZipEntry entry;
|
||||||
while ((entry = zis.getNextEntry()) != null) {
|
while ((entry = zis.getNextEntry()) != null) {
|
||||||
|
if (callback.isCancelled()) return;
|
||||||
if (!entry.isDirectory()) {
|
if (!entry.isDirectory()) {
|
||||||
boolean nameMatched = true;
|
boolean nameMatched = true;
|
||||||
if (patternLower != null && !patternLower.isEmpty()) {
|
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)) {
|
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;
|
org.apache.commons.compress.archivers.tar.TarArchiveEntry entry;
|
||||||
while ((entry = tais.getNextTarEntry()) != null) {
|
while ((entry = tais.getNextTarEntry()) != null) {
|
||||||
|
if (callback.isCancelled()) return;
|
||||||
if (!entry.isDirectory()) {
|
if (!entry.isDirectory()) {
|
||||||
boolean nameMatched = true;
|
boolean nameMatched = true;
|
||||||
if (patternLower != null && !patternLower.isEmpty()) {
|
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)) {
|
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;
|
org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry;
|
||||||
while ((entry = sevenZFile.getNextEntry()) != null) {
|
while ((entry = sevenZFile.getNextEntry()) != null) {
|
||||||
|
if (callback.isCancelled()) return;
|
||||||
if (!entry.isDirectory()) {
|
if (!entry.isDirectory()) {
|
||||||
boolean nameMatched = true;
|
boolean nameMatched = true;
|
||||||
if (patternLower != null && !patternLower.isEmpty()) {
|
if (patternLower != null && !patternLower.isEmpty()) {
|
||||||
@ -858,6 +876,7 @@ public class FileOperations {
|
|||||||
} else if (name.endsWith(".rar")) {
|
} else if (name.endsWith(".rar")) {
|
||||||
try (com.github.junrar.Archive rar = new com.github.junrar.Archive(archive)) {
|
try (com.github.junrar.Archive rar = new com.github.junrar.Archive(archive)) {
|
||||||
for (com.github.junrar.rarfile.FileHeader fh : rar.getFileHeaders()) {
|
for (com.github.junrar.rarfile.FileHeader fh : rar.getFileHeaders()) {
|
||||||
|
if (callback.isCancelled()) return;
|
||||||
if (!fh.isDirectory()) {
|
if (!fh.isDirectory()) {
|
||||||
boolean nameMatched = true;
|
boolean nameMatched = true;
|
||||||
if (patternLower != null && !patternLower.isEmpty()) {
|
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 {
|
public interface SearchCallback {
|
||||||
void onFileFound(File file, String virtualPath);
|
void onFileFound(File file, String virtualPath);
|
||||||
|
default boolean isCancelled() { return false; }
|
||||||
|
default void onProgress(String status) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ public class SearchDialog extends JDialog {
|
|||||||
private JTable resultsTable;
|
private JTable resultsTable;
|
||||||
private ResultsTableModel tableModel;
|
private ResultsTableModel tableModel;
|
||||||
private JButton searchButton;
|
private JButton searchButton;
|
||||||
|
private JButton stopButton;
|
||||||
private JButton cancelButton;
|
private JButton cancelButton;
|
||||||
private JButton viewButton;
|
private JButton viewButton;
|
||||||
private JButton editButton;
|
private JButton editButton;
|
||||||
@ -263,6 +264,14 @@ public class SearchDialog extends JDialog {
|
|||||||
searchButton = new JButton("Search");
|
searchButton = new JButton("Search");
|
||||||
searchButton.addActionListener(e -> performSearch());
|
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 = new JButton("View");
|
||||||
viewButton.setEnabled(false);
|
viewButton.setEnabled(false);
|
||||||
viewButton.addActionListener(e -> viewSelectedFile());
|
viewButton.addActionListener(e -> viewSelectedFile());
|
||||||
@ -272,12 +281,16 @@ public class SearchDialog extends JDialog {
|
|||||||
editButton.addActionListener(e -> editSelectedFile());
|
editButton.addActionListener(e -> editSelectedFile());
|
||||||
|
|
||||||
cancelButton = new JButton("Close");
|
cancelButton = new JButton("Close");
|
||||||
cancelButton.addActionListener(e -> dispose());
|
cancelButton.addActionListener(e -> {
|
||||||
|
searching = false;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
JButton openButton = new JButton("Open location");
|
JButton openButton = new JButton("Open location");
|
||||||
openButton.addActionListener(e -> openSelectedFile());
|
openButton.addActionListener(e -> openSelectedFile());
|
||||||
|
|
||||||
buttonPanel.add(searchButton);
|
buttonPanel.add(searchButton);
|
||||||
|
buttonPanel.add(stopButton);
|
||||||
buttonPanel.add(viewButton);
|
buttonPanel.add(viewButton);
|
||||||
buttonPanel.add(editButton);
|
buttonPanel.add(editButton);
|
||||||
buttonPanel.add(openButton);
|
buttonPanel.add(openButton);
|
||||||
@ -494,6 +507,7 @@ public class SearchDialog extends JDialog {
|
|||||||
|
|
||||||
tableModel.clear();
|
tableModel.clear();
|
||||||
searchButton.setEnabled(false);
|
searchButton.setEnabled(false);
|
||||||
|
stopButton.setEnabled(true);
|
||||||
searching = true;
|
searching = true;
|
||||||
|
|
||||||
// Persist the chosen patterns into history
|
// Persist the chosen patterns into history
|
||||||
@ -544,11 +558,25 @@ public class SearchDialog extends JDialog {
|
|||||||
|
|
||||||
// Spustit vyhledávání v samostatném vlákně
|
// Spustit vyhledávání v samostatném vlákně
|
||||||
SwingWorker<Void, Object[]> worker = new SwingWorker<Void, Object[]>() {
|
SwingWorker<Void, Object[]> worker = new SwingWorker<Void, Object[]>() {
|
||||||
|
private String currentSearchPath = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground() throws Exception {
|
protected Void doInBackground() throws Exception {
|
||||||
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, (file, virtualPath) -> {
|
FileOperations.search(searchDirectory, finalNamePat, finalContentPat, recursiveCheckBox.isSelected(), searchArchives, new FileOperations.SearchCallback() {
|
||||||
if (!searching) return;
|
@Override
|
||||||
publish(new Object[]{file, virtualPath});
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@ -556,25 +584,41 @@ public class SearchDialog extends JDialog {
|
|||||||
@Override
|
@Override
|
||||||
protected void process(List<Object[]> chunks) {
|
protected void process(List<Object[]> chunks) {
|
||||||
for (Object[] chunk : chunks) {
|
for (Object[] chunk : chunks) {
|
||||||
File file = (File) chunk[0];
|
String type = (String) chunk[0];
|
||||||
String virtualPath = (String) chunk[1];
|
if ("file".equals(type)) {
|
||||||
tableModel.addResult(new FileItem(file, virtualPath));
|
File file = (File) chunk[1];
|
||||||
// update found count and status
|
String virtualPath = (String) chunk[2];
|
||||||
foundCount++;
|
tableModel.addResult(new FileItem(file, virtualPath));
|
||||||
statusLabel.setText("Found " + foundCount + " — searching...");
|
foundCount++;
|
||||||
// If this is the first found file, select it and focus the table
|
updateStatusLabel(currentSearchPath);
|
||||||
if (foundCount == 1) {
|
if (foundCount == 1) {
|
||||||
try {
|
try {
|
||||||
resultsTable.setRowSelectionInterval(0, 0);
|
resultsTable.setRowSelectionInterval(0, 0);
|
||||||
resultsTable.requestFocusInWindow();
|
resultsTable.requestFocusInWindow();
|
||||||
} catch (Exception ignore) {}
|
} 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
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
searchButton.setEnabled(true);
|
searchButton.setEnabled(true);
|
||||||
|
stopButton.setEnabled(false);
|
||||||
searching = false;
|
searching = false;
|
||||||
// finalize status
|
// finalize status
|
||||||
statusProgressBar.setIndeterminate(false);
|
statusProgressBar.setIndeterminate(false);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user