better zip info support

This commit is contained in:
rdavidek 2026-04-19 16:06:17 +02:00
parent 2114d2d453
commit 0b23d2424a

View File

@ -1714,9 +1714,11 @@ public class FilePanelTab extends JPanel {
public void showArchiveFile(File archive, String entryName) { public void showArchiveFile(File archive, String entryName) {
rememberArchiveReturnState(archive); rememberArchiveReturnState(archive);
Path temp = extractArchiveToTemp(archive); final String finalEntryName = entryName;
if (temp != null) {
// Delete any previous temp dir if different // Extract archive asynchronously
extractArchiveToTempAsync(archive,
temp -> {
try { try {
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
deleteTempDirRecursively(currentArchiveTempDir); deleteTempDirRecursively(currentArchiveTempDir);
@ -1725,12 +1727,12 @@ public class FilePanelTab extends JPanel {
currentArchiveTempDir = temp; currentArchiveTempDir = temp;
currentArchiveSourceFile = archive; currentArchiveSourceFile = archive;
if (entryName == null || entryName.isBlank()) { if (finalEntryName == null || finalEntryName.isBlank()) {
loadDirectory(temp.toFile(), true, true); loadDirectory(temp.toFile(), true, true);
return; return;
} }
File targetFile = new File(temp.toFile(), entryName); File targetFile = new File(temp.toFile(), finalEntryName);
File targetDir = targetFile.isDirectory() ? targetFile : targetFile.getParentFile(); File targetDir = targetFile.isDirectory() ? targetFile : targetFile.getParentFile();
if (targetDir != null && targetDir.exists()) { if (targetDir != null && targetDir.exists()) {
@ -1742,92 +1744,12 @@ public class FilePanelTab extends JPanel {
} else { } else {
loadDirectory(temp.toFile(), true, true); loadDirectory(temp.toFile(), true, true);
} }
} else { },
error -> {
clearArchiveReturnState(); clearArchiveReturnState();
JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE);
} }
} );
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) {}
@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);
}
} catch (Exception e) {
result[0] = null;
}
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;
} catch (Exception ex) {
// extraction failed; attempt best-effort cleanup
try {
if (currentArchiveTempDir != null) deleteTempDirRecursively(currentArchiveTempDir);
} catch (Exception ignore) {}
currentArchivePassword = null;
return null;
}
} }
private void deleteTempDirRecursively(Path dir) { private void deleteTempDirRecursively(Path dir) {
@ -1881,20 +1803,25 @@ public class FilePanelTab extends JPanel {
navigateUp(); navigateUp();
} else if (FileOperations.isArchiveFile(item.getFile())) { } else if (FileOperations.isArchiveFile(item.getFile())) {
rememberArchiveReturnState(item.getFile()); rememberArchiveReturnState(item.getFile());
Path temp = extractArchiveToTemp(item.getFile()); final File archiveFile = item.getFile();
if (temp != null) {
// Delete any previous temp dir if different // Extract archive asynchronously
extractArchiveToTempAsync(archiveFile,
temp -> {
try { try {
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
deleteTempDirRecursively(currentArchiveTempDir); deleteTempDirRecursively(currentArchiveTempDir);
} }
} catch (Exception ignore) {} } catch (Exception ignore) {}
currentArchiveTempDir = temp; currentArchiveTempDir = temp;
currentArchiveSourceFile = item.getFile(); currentArchiveSourceFile = archiveFile;
loadDirectory(temp.toFile()); loadDirectory(temp.toFile());
} else { },
error -> {
clearArchiveReturnState(); clearArchiveReturnState();
JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE);
} }
);
} else if (item.isDirectory()) { } else if (item.isDirectory()) {
loadDirectory(item.getFile()); loadDirectory(item.getFile());
} else if (item.getFile().isFile()) { } else if (item.getFile().isFile()) {
@ -2400,19 +2327,25 @@ public class FilePanelTab extends JPanel {
navigateUp(); navigateUp();
} else if (FileOperations.isArchiveFile(item.getFile())) { } else if (FileOperations.isArchiveFile(item.getFile())) {
rememberArchiveReturnState(item.getFile()); rememberArchiveReturnState(item.getFile());
Path temp = extractArchiveToTemp(item.getFile()); final File archiveFile = item.getFile();
if (temp != null) {
// Extract archive asynchronously
extractArchiveToTempAsync(archiveFile,
temp -> {
try { try {
if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) { if (currentArchiveTempDir != null && !currentArchiveTempDir.equals(temp)) {
deleteTempDirRecursively(currentArchiveTempDir); deleteTempDirRecursively(currentArchiveTempDir);
} }
} catch (Exception ignore) {} } catch (Exception ignore) {}
currentArchiveTempDir = temp; currentArchiveTempDir = temp;
currentArchiveSourceFile = item.getFile(); currentArchiveSourceFile = archiveFile;
loadDirectory(temp.toFile(), true, true); loadDirectory(temp.toFile(), true, true);
} else { },
error -> {
clearArchiveReturnState(); clearArchiveReturnState();
JOptionPane.showMessageDialog(FilePanelTab.this, error, "Archive Error", JOptionPane.ERROR_MESSAGE);
} }
);
} else if (item.isDirectory()) { } else if (item.isDirectory()) {
loadDirectory(item.getFile()); loadDirectory(item.getFile());
} else if (item.getFile().isFile()) { } else if (item.getFile().isFile()) {
@ -4437,4 +4370,139 @@ public class FilePanelTab extends JPanel {
return null; return null;
} }
} }
/**
* Extract archive asynchronously in background thread with progress dialog
*/
private void extractArchiveToTempAsync(File archive, java.util.function.Consumer<Path> onSuccess, java.util.function.Consumer<String> 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();
}
} }