From 0b23d2424aa04adfaa0949fd6c3e0f62e1bc436a Mon Sep 17 00:00:00 2001 From: rdavidek Date: Sun, 19 Apr 2026 16:06:17 +0200 Subject: [PATCH] better zip info support --- .../cz/kamma/kfmanager/ui/FilePanelTab.java | 340 +++++++++++------- 1 file changed, 204 insertions(+), 136 deletions(-) diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index 2771c06..39e8058 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -1714,120 +1714,42 @@ public class FilePanelTab extends JPanel { public void showArchiveFile(File archive, String entryName) { rememberArchiveReturnState(archive); - Path temp = extractArchiveToTemp(archive); - if (temp != null) { - // Delete any previous temp dir if different - try { - if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { - deleteTempDirRecursively(currentArchiveTempDir); - } - } catch (Exception ignore) {} - currentArchiveTempDir = temp; - currentArchiveSourceFile = archive; - - if (entryName == null || entryName.isBlank()) { - loadDirectory(temp.toFile(), true, true); - return; - } - - File targetFile = new File(temp.toFile(), entryName); - File targetDir = targetFile.isDirectory() ? targetFile : targetFile.getParentFile(); - - if (targetDir != null && targetDir.exists()) { - loadDirectory(targetDir, false, true, () -> { - if (!targetFile.isDirectory()) { - selectItem(targetFile.getName()); + final String finalEntryName = entryName; + + // Extract archive asynchronously + extractArchiveToTempAsync(archive, + temp -> { + try { + if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { + deleteTempDirRecursively(currentArchiveTempDir); } - }); - } else { - loadDirectory(temp.toFile(), true, true); - } - } else { - clearArchiveReturnState(); - } - } + } catch (Exception ignore) {} + currentArchiveTempDir = temp; + currentArchiveSourceFile = archive; - private Path extractArchiveToTemp(File archive) { - if (archive == null || !archive.isFile()) return null; - try { - Path tempDir = Files.createTempDirectory("kfmanager-archive-"); - final String[] usedPassword = new String[1]; - FileOperations.extractArchive(archive, tempDir.toFile(), new FileOperations.ProgressCallback() { - @Override - public void onProgress(long current, long total, String currentFile) {} + if (finalEntryName == null || finalEntryName.isBlank()) { + loadDirectory(temp.toFile(), true, true); + return; + } - @Override - public String requestPassword(String archiveName) { - final String[] result = new String[1]; - try { - Runnable showPasswordDialog = () -> { - JPasswordField pf = new JPasswordField() { - @Override - public void addNotify() { - super.addNotify(); - requestFocusInWindow(); - } - }; - - Object[] message = { - "Enter password for " + archiveName, - pf - }; - - JOptionPane pane = new JOptionPane(message, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); - JDialog dialog = pane.createDialog(FilePanelTab.this, "Password Required"); - - dialog.addWindowFocusListener(new java.awt.event.WindowAdapter() { - @Override - public void windowGainedFocus(java.awt.event.WindowEvent e) { - pf.requestFocusInWindow(); - } - }); - - dialog.setVisible(true); - Object selectedValue = pane.getValue(); - if (selectedValue != null && (Integer) selectedValue == JOptionPane.OK_OPTION) { - result[0] = new String(pf.getPassword()); - usedPassword[0] = result[0]; - } - }; - - if (SwingUtilities.isEventDispatchThread()) { - showPasswordDialog.run(); - } else { - SwingUtilities.invokeAndWait(showPasswordDialog); + File targetFile = new File(temp.toFile(), finalEntryName); + File targetDir = targetFile.isDirectory() ? targetFile : targetFile.getParentFile(); + + if (targetDir != null && targetDir.exists()) { + loadDirectory(targetDir, false, true, () -> { + if (!targetFile.isDirectory()) { + selectItem(targetFile.getName()); } - } catch (Exception e) { - result[0] = null; - } - return result[0]; + }); + } else { + loadDirectory(temp.toFile(), true, true); } - - @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; - } catch (Exception ex) { - // extraction failed; attempt best-effort cleanup - try { - if (currentArchiveTempDir != null) deleteTempDirRecursively(currentArchiveTempDir); - } catch (Exception ignore) {} - currentArchivePassword = null; - return null; - } + }, + error -> { + clearArchiveReturnState(); + JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE); + } + ); } private void deleteTempDirRecursively(Path dir) { @@ -1881,20 +1803,25 @@ public class FilePanelTab extends JPanel { navigateUp(); } else if (FileOperations.isArchiveFile(item.getFile())) { rememberArchiveReturnState(item.getFile()); - Path temp = extractArchiveToTemp(item.getFile()); - if (temp != null) { - // Delete any previous temp dir if different - try { - if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { - deleteTempDirRecursively(currentArchiveTempDir); - } - } catch (Exception ignore) {} - currentArchiveTempDir = temp; - currentArchiveSourceFile = item.getFile(); - loadDirectory(temp.toFile()); - } else { - clearArchiveReturnState(); - } + final File archiveFile = item.getFile(); + + // Extract archive asynchronously + extractArchiveToTempAsync(archiveFile, + temp -> { + try { + if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { + deleteTempDirRecursively(currentArchiveTempDir); + } + } catch (Exception ignore) {} + currentArchiveTempDir = temp; + currentArchiveSourceFile = archiveFile; + loadDirectory(temp.toFile()); + }, + error -> { + clearArchiveReturnState(); + JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE); + } + ); } else if (item.isDirectory()) { loadDirectory(item.getFile()); } else if (item.getFile().isFile()) { @@ -2400,19 +2327,25 @@ public class FilePanelTab extends JPanel { navigateUp(); } else if (FileOperations.isArchiveFile(item.getFile())) { rememberArchiveReturnState(item.getFile()); - Path temp = extractArchiveToTemp(item.getFile()); - if (temp != null) { - try { - if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { - deleteTempDirRecursively(currentArchiveTempDir); - } - } catch (Exception ignore) {} - currentArchiveTempDir = temp; - currentArchiveSourceFile = item.getFile(); - loadDirectory(temp.toFile(), true, true); - } else { - clearArchiveReturnState(); - } + final File archiveFile = item.getFile(); + + // Extract archive asynchronously + extractArchiveToTempAsync(archiveFile, + temp -> { + try { + if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { + deleteTempDirRecursively(currentArchiveTempDir); + } + } catch (Exception ignore) {} + currentArchiveTempDir = temp; + currentArchiveSourceFile = archiveFile; + loadDirectory(temp.toFile(), true, true); + }, + error -> { + clearArchiveReturnState(); + JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE); + } + ); } else if (item.isDirectory()) { loadDirectory(item.getFile()); } else if (item.getFile().isFile()) { @@ -4437,4 +4370,139 @@ public class FilePanelTab extends JPanel { return null; } } + + /** + * Extract archive asynchronously in background thread with progress dialog + */ + private void extractArchiveToTempAsync(File archive, java.util.function.Consumer onSuccess, java.util.function.Consumer onError) { + if (archive == null || !archive.isFile()) { + onError.accept("Archive file not found"); + return; + } + + // Get the Frame owner - traverse up to find JFrame + Frame frameOwner = null; + Component comp = this; + while (comp != null) { + if (comp instanceof Frame) { + frameOwner = (Frame) comp; + break; + } + comp = comp.getParent(); + } + + if (frameOwner == null) { + onError.accept("Cannot determine parent window for progress dialog"); + return; + } + + ProgressDialog progressDialog = new ProgressDialog(frameOwner, "Extracting Archive...", false); + progressDialog.setVisible(true); + + Thread extractionThread = new Thread(() -> { + try { + Path tempDir = java.nio.file.Files.createTempDirectory("kfmanager-archive-"); + final String[] usedPassword = new String[1]; + + FileOperations.extractArchive(archive, tempDir.toFile(), new FileOperations.ProgressCallback() { + @Override + public void onProgress(long current, long total, String currentFile) { + SwingUtilities.invokeLater(() -> { + progressDialog.updateProgress(current, total, currentFile); + }); + } + + @Override + public String requestPassword(String archiveName) { + final String[] result = new String[1]; + try { + progressDialog.setVisible(false); + Runnable showPasswordDialog = () -> { + JPasswordField pf = new JPasswordField() { + @Override + public void addNotify() { + super.addNotify(); + requestFocusInWindow(); + } + }; + + Object[] message = { + "Enter password for " + archiveName, + pf + }; + + JOptionPane pane = new JOptionPane(message, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION); + JDialog dialog = pane.createDialog(FilePanelTab.this, "Password Required"); + + dialog.addWindowFocusListener(new java.awt.event.WindowAdapter() { + @Override + public void windowGainedFocus(java.awt.event.WindowEvent e) { + pf.requestFocusInWindow(); + } + }); + + dialog.setVisible(true); + Object selectedValue = pane.getValue(); + if (selectedValue != null && (Integer) selectedValue == JOptionPane.OK_OPTION) { + result[0] = new String(pf.getPassword()); + usedPassword[0] = result[0]; + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + showPasswordDialog.run(); + } else { + SwingUtilities.invokeAndWait(showPasswordDialog); + } + progressDialog.setVisible(result[0] != null); + } catch (Exception e) { + result[0] = null; + } + return result[0]; + } + + @Override + public void onFileProgress(long current, long total) {} + + @Override + public boolean isCancelled() { + return progressDialog == null || !progressDialog.isVisible(); + } + + @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]; + + SwingUtilities.invokeLater(() -> { + progressDialog.setVisible(false); + progressDialog.dispose(); + onSuccess.accept(tempDir); + }); + } catch (Exception ex) { + ex.printStackTrace(); + SwingUtilities.invokeLater(() -> { + progressDialog.setVisible(false); + progressDialog.dispose(); + onError.accept("Failed to extract archive: " + ex.getMessage()); + }); + } + }, "ArchiveExtractionThread"); + + extractionThread.setDaemon(false); + extractionThread.start(); + } }