diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java index 5399c99..c710c8c 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java @@ -1,36 +1,25 @@ package cz.kamma.kfmanager.service; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.nio.file.AccessDeniedException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.DosFileAttributeView; -import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; import cz.kamma.kfmanager.model.FileItem; import net.lingala.zip4j.ZipFile; -import net.lingala.zip4j.model.FileHeader; /** * Service for file operations - copy, move, delete, etc. @@ -46,19 +35,10 @@ public class FileOperations { } List cleanedItems = cleanDuplicateItems(items); - // Sort items so symlinks are last - cleanedItems.sort((a, b) -> { - boolean aSym = Files.isSymbolicLink(a.getFile().toPath()); - boolean bSym = Files.isSymbolicLink(b.getFile().toPath()); - if (aSym && !bSym) return 1; - if (!aSym && bSym) return -1; - return 0; - }); - + long totalItems = calculateTotalItems(cleanedItems); final long[] currentItem = {0}; final OverwriteResponse[] globalResponse = {null}; - final SymlinkResponse[] globalSymlinkResponse = {null}; for (FileItem item : cleanedItems) { if (callback != null && callback.isCancelled()) break; @@ -88,45 +68,18 @@ public class FileOperations { // Handle directory vs file clash if (target.isDirectory() && !source.isDirectory()) { - deleteDirectoryInternal(target.toPath()); + deleteDirectoryInternal(target); } else if (!target.isDirectory() && source.isDirectory()) { - Files.delete(target.toPath()); + target.delete(); } } while (true) { try { - Path sourcePath = source.toPath(); - if (Files.isSymbolicLink(sourcePath)) { - SymlinkResponse sRes = getSymlinkResponse(source, callback, globalSymlinkResponse); - if (sRes == SymlinkResponse.CANCEL) { - return; - } - if (sRes == SymlinkResponse.IGNORE || sRes == SymlinkResponse.IGNORE_ALL) { - currentItem[0] += countItems(sourcePath); - break; - } - // If FOLLOW, sRes will be FOLLOW or FOLLOW_ALL - if (Files.exists(sourcePath)) { - if (Files.isDirectory(sourcePath)) { - copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null); - // Count the symlink itself as processed to match countItems prediction - currentItem[0]++; - } else { - copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback); - } - } else { - // Link is broken, follow is impossible, copy as link - copySymlink(sourcePath, target.toPath(), totalItems, currentItem, callback); - } - break; - } else if (source.isDirectory()) { - 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]++; + if (source.isDirectory()) { + copyDirectory(source, target, totalItems, currentItem, callback, globalResponse); } else { - copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback); + copyFileWithProgress(source, target, totalItems, currentItem, callback); } break; } catch (IOException e) { @@ -143,51 +96,33 @@ public class FileOperations { } } - private static SymlinkResponse getSymlinkResponse(File source, ProgressCallback callback, SymlinkResponse[] globalSymlinkResponse) { - if (globalSymlinkResponse[0] != null) return globalSymlinkResponse[0]; - if (callback == null) return SymlinkResponse.IGNORE; - SymlinkResponse res = callback.confirmSymlink(source); - if (res == SymlinkResponse.FOLLOW_ALL) globalSymlinkResponse[0] = SymlinkResponse.FOLLOW_ALL; - if (res == SymlinkResponse.IGNORE_ALL) globalSymlinkResponse[0] = SymlinkResponse.IGNORE_ALL; - return res; - } - private static long calculateTotalItems(List items) { long total = 0; for (FileItem item : items) { - total += countItems(item.getFile().toPath()); + total += countItems(item.getFile()); } return total; } - private static long countItems(Path path) { - if (!Files.exists(path, LinkOption.NOFOLLOW_LINKS)) return 0; - if (Files.isSymbolicLink(path)) return 1; - if (!Files.isDirectory(path)) return 1; - final long[] count = {1}; // Start with 1 for the directory itself - try { - Files.walkFileTree(path, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { - if (!dir.equals(path)) count[0]++; - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - count[0]++; - return FileVisitResult.CONTINUE; - } - }); - } catch (IOException ignore) {} - return count[0]; + private static long countItems(File file) { + if (!file.exists()) return 0; + if (!file.isDirectory()) return 1; + long count = 1; // The directory itself + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + count += countItems(child); + } + } + return count; } - private static void copyFileWithProgress(Path source, Path target, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { - long fileSize = Files.size(source); + private static void copyFileWithProgress(File source, File target, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { + long fileSize = source.length(); long bytesCopied = 0; - try (InputStream in = Files.newInputStream(source); - OutputStream out = Files.newOutputStream(target)) { - byte[] buffer = new byte[24576]; + try (InputStream in = new BufferedInputStream(new FileInputStream(source), 65536); + OutputStream out = new BufferedOutputStream(new FileOutputStream(target), 65536)) { + byte[] buffer = new byte[65536]; int length; while ((length = in.read(buffer)) > 0) { if (callback != null && callback.isCancelled()) return; @@ -200,197 +135,93 @@ public class FileOperations { } currentItem[0]++; if (callback != null) { - callback.onProgress(currentItem[0], totalItems, source.getFileName().toString()); + callback.onProgress(currentItem[0], totalItems, source.getName()); callback.onFileProgress(fileSize, fileSize); } - try { - Files.setLastModifiedTime(target, Files.getLastModifiedTime(source)); - try { - Set permissions = Files.getPosixFilePermissions(source); - Files.setPosixFilePermissions(target, permissions); - } catch (UnsupportedOperationException ignore) { - // Not a POSIX filesystem - } - } catch (IOException e) { - // Ignore failure to set time or permissions on some filesystems - } - } - - private static void copySymlink(Path source, Path target, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { - Path linkTarget = Files.readSymbolicLink(source); - Files.deleteIfExists(target); - Files.createSymbolicLink(target, linkTarget); - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, source.getFileName().toString()); - } - } - - private static void copyDirectory(Path source, Path target, long totalItems, final long[] currentItem, ProgressCallback callback, final OverwriteResponse[] globalResponse, final SymlinkResponse[] globalSymlinkResponse, Set visitedPaths) throws IOException { - final Path effectiveSource = Files.isSymbolicLink(source) ? source.toRealPath() : source.toAbsolutePath().normalize(); - final Path finalTarget = target.toAbsolutePath().normalize(); - if (visitedPaths == null) visitedPaths = new HashSet<>(); - if (!visitedPaths.add(effectiveSource)) { - // Cycle detected, skip this directory - return; + target.setLastModified(source.lastModified()); + // Simple permission sync if possible + if (source.canExecute()) target.setExecutable(true); + if (source.canRead()) target.setReadable(true); + if (source.canWrite()) target.setWritable(true); + } + + private static void copyDirectory(File source, File target, long totalItems, final long[] currentItem, ProgressCallback callback, final OverwriteResponse[] globalResponse) throws IOException { + if (!target.exists()) { + if (!target.mkdirs()) { + throw new IOException("Failed to create directory: " + target.getAbsolutePath()); + } } - try { - final Set finalVisitedPaths = visitedPaths; - Files.walkFileTree(effectiveSource, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Path targetDir = finalTarget.resolve(effectiveSource.relativize(dir.toAbsolutePath().normalize())); - if (Files.exists(targetDir, LinkOption.NOFOLLOW_LINKS) && !Files.isDirectory(targetDir, LinkOption.NOFOLLOW_LINKS)) { - Files.delete(targetDir); - } - while (true) { - try { - Files.createDirectories(targetDir); - try { - Set permissions = Files.getPosixFilePermissions(dir); - Files.setPosixFilePermissions(targetDir, permissions); - } catch (UnsupportedOperationException ignore) { - } catch (IOException e) { - // Ignore permission set failures for directories too - } - - if (!dir.toAbsolutePath().normalize().equals(effectiveSource)) { - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, dir.getFileName().toString()); - } - } - return FileVisitResult.CONTINUE; - } catch (IOException e) { - if (callback != null) { - ErrorResponse res = callback.onError(dir.toFile(), e); - if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; - if (res == ErrorResponse.RETRY) continue; - return FileVisitResult.SKIP_SUBTREE; - } - throw e; - } - } - } - - @Override - 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())); + currentItem[0]++; // Count the directory itself + if (callback != null) { + callback.onProgress(currentItem[0], totalItems, source.getName()); + } - if (Files.exists(targetFile, LinkOption.NOFOLLOW_LINKS)) { - if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) return FileVisitResult.CONTINUE; + File[] files = source.listFiles(); + if (files != null) { + for (File file : files) { + if (callback != null && callback.isCancelled()) break; + File targetFile = new File(target, file.getName()); + + if (file.isDirectory()) { + copyDirectory(file, targetFile, totalItems, currentItem, callback, globalResponse); + } else { + if (targetFile.exists()) { + if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) { + currentItem[0] += countItems(file); + continue; + } if (globalResponse[0] != OverwriteResponse.YES_TO_ALL) { - OverwriteResponse res = callback.confirmOverwrite(file.toFile(), targetFile.toFile()); - if (res == OverwriteResponse.CANCEL) return FileVisitResult.TERMINATE; + OverwriteResponse res = callback.confirmOverwrite(file, targetFile); + if (res == OverwriteResponse.CANCEL) return; if (res == OverwriteResponse.NO_TO_ALL) { globalResponse[0] = OverwriteResponse.NO_TO_ALL; - return FileVisitResult.CONTINUE; + currentItem[0] += countItems(file); + continue; + } + if (res == OverwriteResponse.NO) { + currentItem[0] += countItems(file); + continue; } - if (res == OverwriteResponse.NO) return FileVisitResult.CONTINUE; if (res == OverwriteResponse.YES_TO_ALL) globalResponse[0] = OverwriteResponse.YES_TO_ALL; } - // Handle directory vs file clash - if (Files.isDirectory(targetFile, LinkOption.NOFOLLOW_LINKS)) { - deleteDirectoryInternal(targetFile); - } else { - Files.delete(targetFile); - } - } - - while (true) { - try { - if (attrs.isSymbolicLink()) { - SymlinkResponse sRes = getSymlinkResponse(file.toFile(), callback, globalSymlinkResponse); - if (sRes == SymlinkResponse.CANCEL) return FileVisitResult.TERMINATE; - if (sRes == SymlinkResponse.IGNORE || sRes == SymlinkResponse.IGNORE_ALL) { - currentItem[0]++; - return FileVisitResult.CONTINUE; - } - // FOLLOW or FOLLOW_ALL - if (Files.exists(file)) { - if (Files.isDirectory(file)) { - copyDirectory(file, targetFile, totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, finalVisitedPaths); - // Count the symlink itself as processed to match countItems prediction - currentItem[0]++; - } else { - copyFileWithProgress(file, targetFile, totalItems, currentItem, callback); - } - } else { - // Link is broken, follow is impossible, copy as link - copySymlink(file, targetFile, totalItems, currentItem, callback); - } - } else { - copyFileWithProgress(file, targetFile, totalItems, currentItem, callback); - } - return FileVisitResult.CONTINUE; - } catch (IOException e) { - if (callback != null) { - ErrorResponse res = callback.onError(file.toFile(), e); - if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; - if (res == ErrorResponse.RETRY) continue; - return FileVisitResult.CONTINUE; - } - throw e; - } } + copyFileWithProgress(file, targetFile, totalItems, currentItem, callback); } - }); - } finally { - visitedPaths.remove(effectiveSource); - } - } - - private static void deleteDirectoryInternal(Path path) throws IOException { - if (Files.isSymbolicLink(path)) { - deletePathWithForce(path); - return; - } - Files.walkFileTree(path, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - deletePathWithForce(file); - return FileVisitResult.CONTINUE; } - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - deletePathWithForce(dir); - return FileVisitResult.CONTINUE; - } - }); - } - - private static void deletePathWithForce(Path path) throws IOException { - try { - Files.delete(path); - return; - } catch (AccessDeniedException e) { - clearDeletionBlockingAttributes(path); } - Files.delete(path); } - private static void clearDeletionBlockingAttributes(Path path) { + private static void deleteDirectoryInternal(File path) throws IOException { + File[] files = path.listFiles(); + if (files != null) { + for (File child : files) { + if (child.isDirectory()) { + deleteDirectoryInternal(child); + } else { + deleteFileWithForce(child); + } + } + } + deleteFileWithForce(path); + } + + private static void deleteFileWithForce(File file) throws IOException { + if (!file.delete()) { + clearDeletionBlockingAttributes(file); + if (!file.delete()) { + throw new IOException("Failed to delete: " + file.getAbsolutePath()); + } + } + } + + private static void clearDeletionBlockingAttributes(File file) { try { - File file = path.toFile(); if (!file.canWrite()) { file.setWritable(true); } } catch (Exception ignore) {} - - if (cz.kamma.kfmanager.MainApp.CURRENT_OS != cz.kamma.kfmanager.MainApp.OS.WINDOWS) { - return; - } - - try { - DosFileAttributeView dos = Files.getFileAttributeView(path, DosFileAttributeView.class, LinkOption.NOFOLLOW_LINKS); - if (dos != null) { - dos.setReadOnly(false); - dos.setSystem(false); - } - } catch (Exception ignore) {} } /** @@ -402,19 +233,10 @@ public class FileOperations { } List cleanedItems = cleanDuplicateItems(items); - // Sort items so symlinks are last - cleanedItems.sort((a, b) -> { - boolean aSym = Files.isSymbolicLink(a.getFile().toPath()); - boolean bSym = Files.isSymbolicLink(b.getFile().toPath()); - if (aSym && !bSym) return 1; - if (!aSym && bSym) return -1; - return 0; - }); - + long totalItems = calculateTotalItems(cleanedItems); long[] currentItem = {0}; final OverwriteResponse[] globalResponse = {null}; - final SymlinkResponse[] globalSymlinkResponse = {null}; for (FileItem item : cleanedItems) { if (callback != null && callback.isCancelled()) break; @@ -425,8 +247,7 @@ public class FileOperations { continue; } - // Use NOFOLLOW_LINKS for target existence check - if (Files.exists(target.toPath(), LinkOption.NOFOLLOW_LINKS) && !source.getAbsolutePath().equals(target.getAbsolutePath())) { + if (target.exists() && !source.getAbsolutePath().equals(target.getAbsolutePath())) { if (globalResponse[0] == OverwriteResponse.NO_TO_ALL) continue; if (globalResponse[0] != OverwriteResponse.YES_TO_ALL) { OverwriteResponse res = callback.confirmOverwrite(source, target); @@ -441,73 +262,45 @@ public class FileOperations { // Handle directory vs file clash if (target.isDirectory() && !source.isDirectory()) { - deleteDirectoryInternal(target.toPath()); + deleteDirectoryInternal(target); } else if (!target.isDirectory() && source.isDirectory()) { - Files.delete(target.toPath()); + target.delete(); } } - long itemCount = countItems(source.toPath()); + long itemCount = countItems(source); while (true) { try { - Path sourcePath = source.toPath(); - if (Files.isSymbolicLink(sourcePath)) { - SymlinkResponse sRes = getSymlinkResponse(source, callback, globalSymlinkResponse); - if (sRes == SymlinkResponse.CANCEL) return; - if (sRes == SymlinkResponse.IGNORE || sRes == SymlinkResponse.IGNORE_ALL) { - currentItem[0] += itemCount; - break; + if (source.renameTo(target)) { + currentItem[0] += itemCount; + if (callback != null) { + callback.onProgress(currentItem[0], totalItems, source.getName()); } - if (sRes == SymlinkResponse.FOLLOW || sRes == SymlinkResponse.FOLLOW_ALL) { - if (Files.exists(sourcePath)) { - if (Files.isDirectory(sourcePath)) { - copyDirectory(sourcePath, target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null); - currentItem[0]++; - } else { - copyFileWithProgress(sourcePath, target.toPath(), totalItems, currentItem, callback); - } - } else { - // Link is broken, follow is impossible, copy as link - copySymlink(sourcePath, target.toPath(), totalItems, currentItem, callback); - } - Files.delete(sourcePath); - break; + } else { + // Fallback for cross-device moves + if (source.isDirectory()) { + copyDirectory(source, target, totalItems, currentItem, callback, globalResponse); + deleteDirectoryInternal(source); + } else { + copyFileWithProgress(source, target, totalItems, currentItem, callback); + source.delete(); } } - - Files.move(sourcePath, target.toPath(), StandardCopyOption.REPLACE_EXISTING); - currentItem[0] += itemCount; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, source.getName()); - } break; } catch (IOException e) { - // Fallback for cross-device moves (e.g., to network/Samba mounts) - try { - if (source.isDirectory()) { - copyDirectory(source.toPath(), target.toPath(), totalItems, currentItem, callback, globalResponse, globalSymlinkResponse, null); - currentItem[0]++; // For the directory itself which copyDirectory skips - deleteDirectoryInternal(source.toPath()); - } else { - copyFileWithProgress(source.toPath(), target.toPath(), totalItems, currentItem, callback); - Files.delete(source.toPath()); - } + if (callback != null) { + ErrorResponse res = callback.onError(source, e); + if (res == ErrorResponse.ABORT) throw e; + if (res == ErrorResponse.RETRY) continue; break; - } catch (IOException ex) { - if (callback != null) { - ErrorResponse res = callback.onError(source, ex); - if (res == ErrorResponse.ABORT) throw ex; - if (res == ErrorResponse.RETRY) continue; - break; - } else { - throw ex; - } + } else { + throw e; } } } } } - + /** * Delete files/directories */ @@ -522,20 +315,14 @@ public class FileOperations { while (true) { try { - if (Files.isSymbolicLink(file.toPath())) { - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, file.getName()); - } - deletePathWithForce(file.toPath()); - } else if (file.isDirectory()) { - deleteDirectory(file.toPath(), totalItems, currentItem, callback); + if (file.isDirectory()) { + deleteDirectoryRecursive(file, totalItems, currentItem, callback); } else { currentItem[0]++; if (callback != null) { callback.onProgress(currentItem[0], totalItems, file.getName()); } - deletePathWithForce(file.toPath()); + deleteFileWithForce(file); } break; } catch (IOException e) { @@ -551,6 +338,28 @@ public class FileOperations { } } } + + private static void deleteDirectoryRecursive(File directory, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { + File[] children = directory.listFiles(); + if (children != null) { + for (File child : children) { + if (child.isDirectory()) { + deleteDirectoryRecursive(child, totalItems, currentItem, callback); + } else { + currentItem[0]++; + if (callback != null) { + callback.onProgress(currentItem[0], totalItems, child.getName()); + } + deleteFileWithForce(child); + } + } + } + currentItem[0]++; + if (callback != null) { + callback.onProgress(currentItem[0], totalItems, directory.getName()); + } + deleteFileWithForce(directory); + } private static List cleanDuplicateItems(List items) { if (items == null || items.size() <= 1) return items; @@ -594,7 +403,9 @@ public class FileOperations { */ public static void rename(File file, String newName) throws IOException { File target = new File(file.getParentFile(), newName); - Files.move(file.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + if (!file.renameTo(target)) { + throw new IOException("Failed to rename file"); + } } /** @@ -617,86 +428,15 @@ public class FileOperations { } } - private static void setPermissionsFromMode(Path path, int mode) { + public static void setPermissionsFromMode(File file, int mode) { if (mode <= 0 || (cz.kamma.kfmanager.MainApp.CURRENT_OS != cz.kamma.kfmanager.MainApp.OS.LINUX && cz.kamma.kfmanager.MainApp.CURRENT_OS != cz.kamma.kfmanager.MainApp.OS.MACOS)) { return; } - try { - Set perms = new HashSet<>(); - if ((mode & 0400) != 0) perms.add(PosixFilePermission.OWNER_READ); - if ((mode & 0200) != 0) perms.add(PosixFilePermission.OWNER_WRITE); - if ((mode & 0100) != 0) perms.add(PosixFilePermission.OWNER_EXECUTE); - if ((mode & 0040) != 0) perms.add(PosixFilePermission.GROUP_READ); - if ((mode & 0020) != 0) perms.add(PosixFilePermission.GROUP_WRITE); - if ((mode & 0010) != 0) perms.add(PosixFilePermission.GROUP_EXECUTE); - if ((mode & 0004) != 0) perms.add(PosixFilePermission.OTHERS_READ); - if ((mode & 0002) != 0) perms.add(PosixFilePermission.OTHERS_WRITE); - if ((mode & 0001) != 0) perms.add(PosixFilePermission.OTHERS_EXECUTE); - if (!perms.isEmpty()) { - Files.setPosixFilePermissions(path, perms); - } - } catch (Exception ignore) {} - } - - /** - * Delete directory recursively - */ - private static void deleteDirectory(Path directory, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { - if (Files.isSymbolicLink(directory)) { - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, directory.getFileName().toString()); - } - deletePathWithForce(directory); - return; - } - Files.walkFileTree(directory, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - while (true) { - try { - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, file.getFileName().toString()); - } - deletePathWithForce(file); - return FileVisitResult.CONTINUE; - } catch (IOException e) { - if (callback != null) { - ErrorResponse res = callback.onError(file.toFile(), e); - if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; - if (res == ErrorResponse.RETRY) continue; - return FileVisitResult.CONTINUE; - } - throw e; - } - } - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - if (exc != null) throw exc; - while (true) { - try { - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, dir.getFileName().toString()); - } - deletePathWithForce(dir); - return FileVisitResult.CONTINUE; - } catch (IOException e) { - if (callback != null) { - ErrorResponse res = callback.onError(dir.toFile(), e); - if (res == ErrorResponse.ABORT) return FileVisitResult.TERMINATE; - if (res == ErrorResponse.RETRY) continue; - return FileVisitResult.CONTINUE; - } - throw e; - } - } - } - }); + // Using basic java.io.File permissions as NIO PosixFilePermission is gone + file.setReadable((mode & 0444) != 0, (mode & 0044) == 0); + file.setWritable((mode & 0222) != 0, (mode & 0022) == 0); + file.setExecutable((mode & 0111) != 0, (mode & 0011) == 0); } /** @@ -747,16 +487,13 @@ public class FileOperations { } } - searchRecursive(directory.toPath(), filenameLower, filenameRegex, contentPattern, maxDepth, 0, searchArchives, caseSensitive, callback); + searchRecursive(directory, filenameLower, filenameRegex, contentPattern, maxDepth, 0, searchArchives, caseSensitive, callback); } - private static void searchRecursive(Path directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, int maxDepth, int currentDepth, boolean searchArchives, boolean caseSensitive, SearchCallback callback) throws IOException { + private static void searchRecursive(File directory, String patternLower, Pattern filenameRegex, Pattern contentPattern, int maxDepth, int currentDepth, boolean searchArchives, boolean caseSensitive, 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(); - + String pathStr = directory.getAbsolutePath(); callback.onProgress(pathStr); // Skip troublesome virtual filesystems and device nodes @@ -764,21 +501,16 @@ public class FileOperations { return; } - try (DirectoryStream stream = Files.newDirectoryStream(directory)) { - for (Path entry : stream) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { 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 (file.isDirectory()) { if (maxDepth == -1 || currentDepth < maxDepth) { - searchRecursive(entry, patternLower, filenameRegex, contentPattern, maxDepth, currentDepth + 1, searchArchives, caseSensitive, callback); + searchRecursive(file, patternLower, filenameRegex, contentPattern, maxDepth, currentDepth + 1, searchArchives, caseSensitive, callback); } } else { - File file = entry.toFile(); boolean nameMatched = true; if (patternLower != null && !patternLower.isEmpty()) { nameMatched = matchName(file.getName(), patternLower, filenameRegex, caseSensitive); @@ -786,7 +518,7 @@ public class FileOperations { boolean contentMatched = true; if (nameMatched && contentPattern != null) { - contentMatched = fileMatchesContent(entry, contentPattern); + contentMatched = fileMatchesContent(file, contentPattern); } if (nameMatched && contentMatched) { @@ -798,12 +530,10 @@ public class FileOperations { searchInArchiveCombined(file, patternLower, filenameRegex, contentPattern, caseSensitive, callback); } } - } catch (AccessDeniedException e) { + } catch (Exception e) { // Silently skip } } - } catch (AccessDeniedException e) { - // Silently skip } } @@ -818,8 +548,8 @@ public class FileOperations { } } - private static boolean fileMatchesContent(Path entry, Pattern contentPattern) { - try (BufferedReader br = Files.newBufferedReader(entry)) { + private static boolean fileMatchesContent(File file, Pattern contentPattern) { + try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while ((line = br.readLine()) != null) { if (contentPattern.matcher(line).find()) { @@ -844,7 +574,7 @@ public class FileOperations { while ((entry = zis.getNextEntry()) != null) { if (entry.getName().equals(entryPath) || (archive.getAbsolutePath() + File.separator + entry.getName()).equals(entryPath)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[24576]; + byte[] buffer = new byte[65536]; int len; while ((len = zis.read(buffer)) > 0) { baos.write(buffer, 0, len); @@ -863,7 +593,7 @@ public class FileOperations { while ((entry = tais.getNextTarEntry()) != null) { if (entry.getName().equals(entryPath) || (archive.getAbsolutePath() + File.separator + entry.getName()).equals(entryPath)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[24576]; + byte[] buffer = new byte[65536]; int len; while ((len = tais.read(buffer)) > 0) { baos.write(buffer, 0, len); @@ -878,7 +608,7 @@ public class FileOperations { while ((entry = sevenZFile.getNextEntry()) != null) { if (entry.getName().equals(entryPath) || (archive.getAbsolutePath() + File.separator + entry.getName()).equals(entryPath)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[24576]; + byte[] buffer = new byte[65536]; int len; while ((len = sevenZFile.read(buffer)) > 0) { baos.write(buffer, 0, len); @@ -1082,7 +812,7 @@ public class FileOperations { if (file.isDirectory()) { zipFile.addFolder(file, currentParams); // Update progress correctly for directories - currentItem[0] += countItems(file.toPath()); + currentItem[0] += countItems(file); } else { zipFile.addFile(file, currentParams); currentItem[0]++; @@ -1096,331 +826,211 @@ public class FileOperations { } /** - * True when archive can be rewritten from extracted temp directory. + * Enum for search results */ - public static boolean supportsArchiveRewrite(File archiveFile) { - if (archiveFile == null) return false; - String n = archiveFile.getName().toLowerCase(); - return n.endsWith(".zip") || n.endsWith(".jar") || n.endsWith(".war"); + public interface SearchCallback { + void onFileFound(File file, String pathInArchive); + void onProgress(String currentPath); + boolean isCancelled(); } - /** - * Rebuild zip-like archive from already extracted directory content. - */ - public static void rewriteArchiveFromDirectory(File extractedRootDir, File targetArchiveFile, ProgressCallback callback) throws IOException { - rewriteArchiveFromDirectory(extractedRootDir, targetArchiveFile, null, callback); + public interface ProgressCallback { + void onProgress(long current, long total, String fileName); + void onFileProgress(long current, long total); + boolean isCancelled(); + OverwriteResponse confirmOverwrite(File source, File target); + ErrorResponse onError(File file, Exception e); + SymlinkResponse confirmSymlink(File file); + String requestPassword(String archiveName); } - public static void rewriteArchiveFromDirectory(File extractedRootDir, File targetArchiveFile, String password, ProgressCallback callback) throws IOException { - if (extractedRootDir == null || !extractedRootDir.isDirectory()) { - throw new IOException("Extracted archive directory does not exist"); - } - if (targetArchiveFile == null) { - throw new IOException("Target archive is not defined"); - } - if (!supportsArchiveRewrite(targetArchiveFile)) { - throw new IOException("Updating this archive format is not supported: " + targetArchiveFile.getName()); + public static void extractArchive(File archive, File targetDir, ProgressCallback callback) throws IOException { + if (!targetDir.exists() && !targetDir.mkdirs()) { + throw new IOException("Failed to create target directory: " + targetDir); } - boolean archiveEncrypted = false; - if (targetArchiveFile.exists()) { - try (ZipFile existing = new ZipFile(targetArchiveFile)) { - archiveEncrypted = existing.isEncrypted(); - } catch (IOException e) { - throw e; - } catch (Exception e) { - throw new IOException("Cannot inspect archive: " + e.getMessage(), e); - } - } - if (archiveEncrypted && (password == null || password.isBlank())) { - throw new IOException("Password is required to update encrypted archive"); - } - - Path tempArchivePath = Files.createTempFile("kfmanager-archive-sync-", ".zip"); - File tempArchiveFile = tempArchivePath.toFile(); + String name = archive.getName().toLowerCase(); try { - File[] children = extractedRootDir.listFiles(); - if (children == null || children.length == 0) { - if (archiveEncrypted) { - throw new IOException("Updating encrypted archive to empty content is not supported"); - } - createEmptyZip(tempArchiveFile); + if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { + extractZip(archive, targetDir, callback); + } else if (name.endsWith(".7z")) { + extract7z(archive, targetDir, callback); + } else if (name.endsWith(".rar")) { + extractRar(archive, targetDir, callback); + } else if (name.endsWith(".tar") || name.endsWith(".tar.gz") || name.endsWith(".tgz")) { + extractTar(archive, targetDir, callback); } else { - List items = new ArrayList<>(); - for (File child : children) { - items.add(new FileItem(child)); + throw new IOException("Unsupported archive format: " + name); + } + } catch (Exception e) { + if (callback != null) { + callback.onError(archive, e); + } + throw new IOException("Extraction failed: " + e.getMessage(), e); + } + } + + private static void extractZip(File archive, File targetDir, ProgressCallback callback) throws IOException { + String password = null; + try (ZipFile zipFile = new ZipFile(archive)) { + if (zipFile.isEncrypted()) { + password = callback.requestPassword(archive.getName()); + if (password == null) return; + zipFile.setPassword(password.toCharArray()); + } + + List headers = zipFile.getFileHeaders(); + long total = headers.size(); + for (int i = 0; i < headers.size(); i++) { + if (callback != null && callback.isCancelled()) break; + net.lingala.zip4j.model.FileHeader header = headers.get(i); + zipFile.extractFile(header, targetDir.getAbsolutePath()); + if (callback != null) { + callback.onProgress(i + 1, total, header.getFileName()); } - zip(items, tempArchiveFile, archiveEncrypted ? password : null, callback); - } - - try { - Files.move(tempArchivePath, targetArchiveFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException moveError) { - throw new IOException("Failed to update archive " + targetArchiveFile.getName() + ": " + moveError.getMessage(), moveError); - } - } finally { - try { - Files.deleteIfExists(tempArchivePath); - } catch (Exception ignore) { } } } - private static void createEmptyZip(File targetZipFile) throws IOException { - if (targetZipFile.exists() && !targetZipFile.delete()) { - throw new IOException("Cannot recreate archive: " + targetZipFile.getAbsolutePath()); - } - try (OutputStream os = Files.newOutputStream(targetZipFile.toPath()); - ZipOutputStream zos = new ZipOutputStream(os)) { - // intentionally empty; valid empty ZIP archive - } - } - - /** - * Extract an archive into a target directory - */ - public static void extractArchive(File archiveFile, File targetDirectory, ProgressCallback callback) throws Exception { - if (!targetDirectory.exists()) { - Files.createDirectories(targetDirectory.toPath()); - } + private static void extract7z(File archive, File targetDir, ProgressCallback callback) throws IOException { + try (org.apache.commons.compress.archivers.sevenz.SevenZFile sevenZFile = new org.apache.commons.compress.archivers.sevenz.SevenZFile(archive)) { + Iterable entries = sevenZFile.getEntries(); + List entryList = new ArrayList<>(); + for (org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry : entries) { + entryList.add(entry); + } - String name = archiveFile.getName().toLowerCase(); - if (name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war")) { - unzip(archiveFile, targetDirectory, callback); - } else if (name.endsWith(".tar.gz") || name.endsWith(".tgz")) { - extractTarGz(archiveFile, targetDirectory, callback); - } else if (name.endsWith(".tar")) { - extractTar(archiveFile, targetDirectory, callback); - } else if (name.endsWith(".7z")) { - extractSevenZ(archiveFile, targetDirectory, callback); - } else if (name.endsWith(".rar")) { - extractRar(archiveFile, targetDirectory, callback); - } else { - throw new IOException("Unsupported archive format: " + archiveFile.getName()); + long total = entryList.size(); + org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry; + int count = 0; + while ((entry = sevenZFile.getNextEntry()) != null) { + if (callback != null && callback.isCancelled()) break; + File targetFile = new File(targetDir, entry.getName()); + if (entry.isDirectory()) { + targetFile.mkdirs(); + } else { + targetFile.getParentFile().mkdirs(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile), 65536)) { + byte[] buffer = new byte[65536]; + int len; + while ((len = sevenZFile.read(buffer)) > 0) { + out.write(buffer, 0, len); + } + } + } + count++; + if (callback != null) { + callback.onProgress(count, total, entry.getName()); + } + } } } - private static void extractTarGz(File archive, File targetDir, ProgressCallback callback) throws IOException { - try (InputStream fis = Files.newInputStream(archive.toPath()); - InputStream gzis = new org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream(fis); - org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(gzis)) { - extractTarInternal(tais, targetDir, callback); + private static void extractRar(File archive, File targetDir, ProgressCallback callback) throws IOException { + try (com.github.junrar.Archive rar = new com.github.junrar.Archive(archive)) { + List headers = rar.getFileHeaders(); + long total = headers.size(); + for (int i = 0; i < headers.size(); i++) { + if (callback != null && callback.isCancelled()) break; + com.github.junrar.rarfile.FileHeader header = headers.get(i); + File targetFile = new File(targetDir, header.getFileName()); + if (header.isDirectory()) { + targetFile.mkdirs(); + } else { + targetFile.getParentFile().mkdirs(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile), 65536)) { + rar.extractFile(header, out); + } + } + if (callback != null) { + callback.onProgress(i + 1, total, header.getFileName()); + } + } + } catch (com.github.junrar.exception.RarException e) { + throw new IOException(e); } } private static void extractTar(File archive, File targetDir, ProgressCallback callback) throws IOException { - try (InputStream fis = Files.newInputStream(archive.toPath()); - org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais = new org.apache.commons.compress.archivers.tar.TarArchiveInputStream(fis)) { - extractTarInternal(tais, targetDir, callback); + InputStream is = new BufferedInputStream(new FileInputStream(archive), 65536); + if (archive.getName().toLowerCase().endsWith(".gz") || archive.getName().toLowerCase().endsWith(".tgz")) { + is = new org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream(is); } - } - - private static void extractTarInternal(org.apache.commons.compress.archivers.tar.TarArchiveInputStream tais, File targetDir, ProgressCallback callback) throws IOException { - org.apache.commons.compress.archivers.tar.TarArchiveEntry entry; - long current = 0; - while ((entry = tais.getNextTarEntry()) != null) { - current++; - if (callback != null) callback.onProgress(current, -1, entry.getName()); - - File newFile = new File(targetDir, entry.getName()); - if (entry.isDirectory()) { - newFile.mkdirs(); - setPermissionsFromMode(newFile.toPath(), entry.getMode()); - } else { - newFile.getParentFile().mkdirs(); - Files.copy(tais, newFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - setPermissionsFromMode(newFile.toPath(), entry.getMode()); - } - } - } - - private static void extractSevenZ(File archive, File targetDir, ProgressCallback callback) throws IOException { - char[] password = null; - while (true) { - try (org.apache.commons.compress.archivers.sevenz.SevenZFile sevenZFile = - (password == null) ? new org.apache.commons.compress.archivers.sevenz.SevenZFile(archive) : - new org.apache.commons.compress.archivers.sevenz.SevenZFile(archive, password)) { - org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry entry; - long current = 0; - while ((entry = sevenZFile.getNextEntry()) != null) { - current++; - if (callback != null) callback.onProgress(current, -1, entry.getName()); - - File newFile = new File(targetDir, entry.getName()); - if (entry.isDirectory()) { - newFile.mkdirs(); - } else { - newFile.getParentFile().mkdirs(); - try (OutputStream os = Files.newOutputStream(newFile.toPath())) { - byte[] buffer = new byte[8192]; - int bytesRead; - while ((bytesRead = sevenZFile.read(buffer)) != -1) { - os.write(buffer, 0, bytesRead); - } - } - } - } - break; - } catch (IOException e) { - if (e.getMessage() != null && (e.getMessage().toLowerCase().contains("password") || e.getMessage().toLowerCase().contains("encrypted"))) { - String pass = (callback != null) ? callback.requestPassword(archive.getName()) : null; - if (pass == null) throw new IOException("Password required for " + archive.getName()); - password = pass.toCharArray(); + 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; + // Tar doesn't easily give total count without pre-scanning + while ((entry = tais.getNextTarEntry()) != null) { + if (callback != null && callback.isCancelled()) break; + File targetFile = new File(targetDir, entry.getName()); + if (entry.isDirectory()) { + targetFile.mkdirs(); } else { - throw e; - } - } - } - } - - private static void extractRar(File archiveFile, File targetDir, ProgressCallback callback) throws Exception { - String password = null; - while (true) { - try (com.github.junrar.Archive archive = (password == null) ? new com.github.junrar.Archive(archiveFile) : new com.github.junrar.Archive(archiveFile, password)) { - if (archive.isEncrypted() && password == null) { - password = (callback != null) ? callback.requestPassword(archiveFile.getName()) : null; - if (password == null) throw new IOException("Password required"); - continue; - } - com.github.junrar.rarfile.FileHeader fh = archive.nextFileHeader(); - long current = 0; - while (fh != null) { - if (fh.isEncrypted() && password == null) { - password = (callback != null) ? callback.requestPassword(archiveFile.getName()) : null; - if (password == null) throw new IOException("Password required"); - break; // exit inner loop to reopen with password - } - current++; - String entryName = fh.getFileName().replace('\\', '/'); - if (callback != null) callback.onProgress(current, -1, entryName); - - File newFile = new File(targetDir, entryName); - if (fh.isDirectory()) { - newFile.mkdirs(); - } else { - newFile.getParentFile().mkdirs(); - try (OutputStream os = Files.newOutputStream(newFile.toPath())) { - archive.extractFile(fh, os); + targetFile.getParentFile().mkdirs(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile), 65536)) { + byte[] buffer = new byte[65536]; + int len; + while ((len = tais.read(buffer)) > 0) { + out.write(buffer, 0, len); } } - fh = archive.nextFileHeader(); } - if (fh == null) break; // Finished successfully - } - } - } - - /** - * Unzip a zip file into a target directory - */ - public static void unzip(File zipFile, File targetDirectory, ProgressCallback callback) throws IOException { - if (!targetDirectory.exists()) { - Files.createDirectories(targetDirectory.toPath()); - } - - ZipFile zf = null; - String password = null; - - try { - zf = new ZipFile(zipFile); - - // Check if archive is encrypted and request password - if (zf.isEncrypted()) { - password = (callback != null) ? callback.requestPassword(zipFile.getName()) : null; - if (password == null) { - throw new IOException("Password required for " + zipFile.getName()); - } - zf.setPassword(password.toCharArray()); - } - - List entries = zf.getFileHeaders(); - long totalItems = entries.size(); - long currentItem = 0; - - for (FileHeader entry : entries) { - currentItem++; - File newFile = new File(targetDirectory, entry.getFileName()); - if (callback != null) { - callback.onProgress(currentItem, totalItems, entry.getFileName()); - if (callback.isCancelled()) break; - } - - try { - if (entry.isDirectory()) { - if (!newFile.isDirectory() && !newFile.mkdirs()) { - throw new IOException("Failed to create directory " + newFile); - } - } else { - File parent = newFile.getParentFile(); - if (parent != null && !parent.exists()) { - parent.mkdirs(); - } - zf.extractFile(entry, targetDirectory.getAbsolutePath()); - } - } catch (Exception e) { - // Check if this is a password-related error on an encrypted file - if (entry.isEncrypted() && e.getMessage() != null && - (e.getMessage().toLowerCase().contains("password") || - e.getMessage().toLowerCase().contains("decrypt") || - e.getMessage().toLowerCase().contains("checksum"))) { - throw new IOException("Incorrect password for encrypted archive", e); - } - // For other errors, skip the file and continue - System.err.println("Warning: Failed to extract " + entry.getFileName() + ": " + e.getMessage()); - } - } - } catch (Exception e) { - if (e instanceof IOException && e.getMessage() != null && - (e.getMessage().contains("Password required") || - e.getMessage().contains("Incorrect password"))) { - throw (IOException) e; - } - throw new IOException("Failed to unzip: " + e.getMessage(), e); - } finally { - if (zf != null) { - try { - zf.close(); - } catch (Exception e) { - // Ignore errors during close + callback.onProgress(0, 0, entry.getName()); // 0/0 because we don't know total } } } } - - // legacy matchesPattern removed — filename wildcard handling is done via a precompiled Pattern - - /** - * Callback for operation progress - */ - public enum OverwriteResponse { - YES, NO, YES_TO_ALL, NO_TO_ALL, CANCEL + + public static boolean supportsArchiveRewrite(File archive) { + if (archive == null) return false; + String name = archive.getName().toLowerCase(); + return name.endsWith(".zip") || name.endsWith(".jar") || name.endsWith(".war"); } - public enum SymlinkResponse { - FOLLOW, IGNORE, FOLLOW_ALL, IGNORE_ALL, CANCEL + public static void rewriteArchiveFromDirectory(File sourceDir, File targetArchive, String password, ProgressCallback callback) throws IOException { + if (!sourceDir.exists() || !sourceDir.isDirectory()) { + throw new IOException("Source directory for rewrite does not exist"); + } + + if (targetArchive.exists()) { + targetArchive.delete(); + } + + try (ZipFile zipFile = new ZipFile(targetArchive)) { + if (password != null && !password.isEmpty()) { + zipFile.setPassword(password.toCharArray()); + } + + net.lingala.zip4j.model.ZipParameters params = new net.lingala.zip4j.model.ZipParameters(); + if (password != null && !password.isEmpty()) { + params.setEncryptFiles(true); + params.setEncryptionMethod(net.lingala.zip4j.model.enums.EncryptionMethod.AES); + params.setAesKeyStrength(net.lingala.zip4j.model.enums.AesKeyStrength.KEY_STRENGTH_256); + } + + File[] children = sourceDir.listFiles(); + if (children != null) { + for (File child : children) { + if (callback != null && callback.isCancelled()) break; + if (child.isDirectory()) { + zipFile.addFolder(child, params); + } else { + zipFile.addFile(child, params); + } + } + } + } + } + + public enum OverwriteResponse { + YES, YES_TO_ALL, NO, NO_TO_ALL, CANCEL } public enum ErrorResponse { - SKIP, RETRY, ABORT + RETRY, SKIP, ABORT } - public interface ProgressCallback { - void onProgress(long current, long total, String currentFile); - default void onFileProgress(long current, long total) {} - default boolean isCancelled() { return false; } - default OverwriteResponse confirmOverwrite(File source, File destination) { return OverwriteResponse.YES; } - default SymlinkResponse confirmSymlink(File symlink) { return SymlinkResponse.IGNORE; } - default ErrorResponse onError(File file, Exception e) { return ErrorResponse.ABORT; } - default String requestPassword(String archiveName) { return null; } - } - - /** - * Callback for search - */ - public interface SearchCallback { - void onFileFound(File file, String virtualPath); - default boolean isCancelled() { return false; } - default void onProgress(String status) {} + public enum SymlinkResponse { + FOLLOW, FOLLOW_ALL, IGNORE, IGNORE_ALL, CANCEL } } diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index e6a1985..f69ee99 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -1721,6 +1721,21 @@ public class FilePanelTab extends JPanel { } return result[0]; } + + @Override + public void onFileProgress(long current, long total) {} + + @Override + public boolean isCancelled() { return false; } + + @Override + public FileOperations.OverwriteResponse confirmOverwrite(File source, File target) { return FileOperations.OverwriteResponse.YES; } + + @Override + public FileOperations.ErrorResponse onError(File file, Exception e) { return FileOperations.ErrorResponse.ABORT; } + + @Override + public FileOperations.SymlinkResponse confirmSymlink(File file) { return FileOperations.SymlinkResponse.FOLLOW; } }); currentArchivePassword = usedPassword[0]; return tempDir; @@ -2215,6 +2230,18 @@ public class FilePanelTab extends JPanel { } return result[0]; } + + @Override + public void onFileProgress(long current, long total) {} + + @Override + public FileOperations.OverwriteResponse confirmOverwrite(File source, File target) { return FileOperations.OverwriteResponse.YES; } + + @Override + public FileOperations.SymlinkResponse confirmSymlink(File file) { return FileOperations.SymlinkResponse.FOLLOW; } + + @Override + public String requestPassword(String archiveName) { return null; } }); SwingUtilities.invokeLater(() -> { progressDialog.dispose(); @@ -2767,6 +2794,12 @@ public class FilePanelTab extends JPanel { } return result[0]; } + + @Override + public FileOperations.SymlinkResponse confirmSymlink(File file) { return FileOperations.SymlinkResponse.FOLLOW; } + + @Override + public String requestPassword(String archiveName) { return null; } }; if (action == ClipboardService.ClipboardAction.CUT) {