From 62162af00eb95bd617c228f5ed9a4e068d199753 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Wed, 18 Mar 2026 15:05:39 +0100 Subject: [PATCH] archive encrytpion added --- .../kfmanager/service/FileOperationQueue.java | 41 ++++++++ .../kfmanager/service/FileOperations.java | 98 +++++++------------ .../cz/kamma/kfmanager/ui/MainWindow.java | 75 ++++++++++++-- 3 files changed, 143 insertions(+), 71 deletions(-) diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java b/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java index d349dbc..0088291 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperationQueue.java @@ -157,6 +157,47 @@ public class FileOperationQueue { // For background queue, maybe skip on error? return FileOperations.ErrorResponse.SKIP; } + + @Override + public String requestPassword(String archiveName) { + final String[] result = {null}; + try { + javax.swing.SwingUtilities.invokeAndWait(() -> { + javax.swing.JPasswordField pf = new javax.swing.JPasswordField() { + @Override + public void addNotify() { + super.addNotify(); + requestFocusInWindow(); + } + }; + + Object[] message = { + "Enter password for " + archiveName, + pf + }; + + javax.swing.JOptionPane pane = new javax.swing.JOptionPane(message, javax.swing.JOptionPane.PLAIN_MESSAGE, javax.swing.JOptionPane.OK_CANCEL_OPTION); + javax.swing.JDialog dialog = pane.createDialog(null, "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 == javax.swing.JOptionPane.OK_OPTION) { + result[0] = new String(pf.getPassword()); + } + }); + } catch (Exception e) { + return null; + } + return result[0]; + } }); if (task.status != OperationStatus.CANCELLED) { diff --git a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java index dcbde79..fdfc046 100644 --- a/src/main/java/cz/kamma/kfmanager/service/FileOperations.java +++ b/src/main/java/cz/kamma/kfmanager/service/FileOperations.java @@ -1045,78 +1045,52 @@ public class FileOperations { * Zip files/directories into a target zip file */ public static void zip(List items, File targetZipFile, ProgressCallback callback) throws IOException { + zip(items, targetZipFile, null, callback); + } + + /** + * Zip files/directories into a target zip file with optional password + */ + public static void zip(List items, File targetZipFile, String password, ProgressCallback callback) throws IOException { List cleanedItems = cleanDuplicateItems(items); long totalItems = calculateTotalItems(cleanedItems); long[] currentItem = {0}; - try (org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream zos = new org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream(targetZipFile)) { + net.lingala.zip4j.model.ZipParameters zipParameters = new net.lingala.zip4j.model.ZipParameters(); + if (password != null && !password.isEmpty()) { + zipParameters.setEncryptFiles(true); + zipParameters.setEncryptionMethod(net.lingala.zip4j.model.enums.EncryptionMethod.AES); + zipParameters.setAesKeyStrength(net.lingala.zip4j.model.enums.AesKeyStrength.KEY_STRENGTH_256); + } + + if (targetZipFile.exists()) { + targetZipFile.delete(); + } + + try (ZipFile zipFile = new ZipFile(targetZipFile)) { + if (password != null && !password.isEmpty()) { + zipFile.setPassword(password.toCharArray()); + } + for (FileItem item : cleanedItems) { if (callback != null && callback.isCancelled()) break; - addToZip(item.getFile(), item.getName(), zos, totalItems, currentItem, callback); - } - } - } - - private static void addToZip(File fileToZip, String fileName, org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream zos, long totalItems, long[] currentItem, ProgressCallback callback) throws IOException { - if (fileToZip.isHidden()) { - return; - } - - currentItem[0]++; - if (callback != null) { - callback.onProgress(currentItem[0], totalItems, fileName); - } - - org.apache.commons.compress.archivers.zip.ZipArchiveEntry zipEntry = new org.apache.commons.compress.archivers.zip.ZipArchiveEntry(fileToZip, fileName); - - // Try to set POSIX permissions - if (cz.kamma.kfmanager.MainApp.CURRENT_OS == cz.kamma.kfmanager.MainApp.OS.LINUX || - cz.kamma.kfmanager.MainApp.CURRENT_OS == cz.kamma.kfmanager.MainApp.OS.MACOS) { - try { - Set perms = Files.getPosixFilePermissions(fileToZip.toPath()); - int mode = 0; - if (perms.contains(PosixFilePermission.OWNER_READ)) mode |= 0400; - if (perms.contains(PosixFilePermission.OWNER_WRITE)) mode |= 0200; - if (perms.contains(PosixFilePermission.OWNER_EXECUTE)) mode |= 0100; - if (perms.contains(PosixFilePermission.GROUP_READ)) mode |= 0040; - if (perms.contains(PosixFilePermission.GROUP_WRITE)) mode |= 0020; - if (perms.contains(PosixFilePermission.GROUP_EXECUTE)) mode |= 0010; - if (perms.contains(PosixFilePermission.OTHERS_READ)) mode |= 0004; - if (perms.contains(PosixFilePermission.OTHERS_WRITE)) mode |= 0002; - if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) mode |= 0001; - zipEntry.setUnixMode(mode); - } catch (Exception ignore) {} - } - - if (fileToZip.isDirectory()) { - zos.putArchiveEntry(zipEntry); - zos.closeArchiveEntry(); - File[] children = fileToZip.listFiles(); - if (children != null) { - for (File childFile : children) { - addToZip(childFile, fileName + (fileName.endsWith("/") ? "" : "/") + childFile.getName(), zos, totalItems, currentItem, callback); + + File file = item.getFile(); + net.lingala.zip4j.model.ZipParameters currentParams = new net.lingala.zip4j.model.ZipParameters(zipParameters); + + if (file.isDirectory()) { + zipFile.addFolder(file, currentParams); + // Update progress correctly for directories + currentItem[0] += countItems(file.toPath()); + } else { + zipFile.addFile(file, currentParams); + currentItem[0]++; } - } - return; - } - - try (InputStream is = Files.newInputStream(fileToZip.toPath())) { - zos.putArchiveEntry(zipEntry); - long fileSize = fileToZip.length(); - long bytesCopied = 0; - byte[] buffer = new byte[24576]; - int len; - while ((len = is.read(buffer)) >= 0) { - zos.write(buffer, 0, len); - bytesCopied += len; + if (callback != null) { - callback.onFileProgress(bytesCopied, fileSize); + callback.onProgress(currentItem[0], totalItems, file.getName()); } } - if (callback != null) { - callback.onFileProgress(fileSize, fileSize); - } - zos.closeArchiveEntry(); } } diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index ad74eac..efa9bcc 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -9,6 +9,8 @@ import cz.kamma.kfmanager.service.FileOperationQueue; import javax.swing.*; import javax.swing.filechooser.FileSystemView; +import javax.swing.event.AncestorListener; +import javax.swing.event.AncestorEvent; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.datatransfer.DataFlavor; @@ -1946,18 +1948,48 @@ public class MainWindow extends JFrame { } defaultName += ".zip"; - String zipName = JOptionPane.showInputDialog(this, "Enter zip filename:", defaultName); - if (zipName == null || zipName.trim().isEmpty()) { - if (activePanel != null && activePanel.getFileTable() != null) { - activePanel.getFileTable().requestFocusInWindow(); + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.insets = new Insets(5, 5, 5, 5); + gbc.gridx = 0; + gbc.gridy = 0; + panel.add(new JLabel("Enter zip filename:"), gbc); + + gbc.gridy = 1; + JTextField zipNameField = new JTextField(defaultName, 20); + panel.add(zipNameField, gbc); + + gbc.gridy = 2; + JCheckBox encryptCheckBox = new JCheckBox("Encrypt with password"); + encryptCheckBox.setMnemonic(KeyEvent.VK_E); + panel.add(encryptCheckBox, gbc); + + gbc.gridy = 3; + JPasswordField passwordField = new JPasswordField(20); + passwordField.setEnabled(false); + panel.add(passwordField, gbc); + + encryptCheckBox.addActionListener(e -> { + passwordField.setEnabled(encryptCheckBox.isSelected()); + if (encryptCheckBox.isSelected()) { + passwordField.requestFocusInWindow(); } + }); + + int option = JOptionPane.showConfirmDialog(this, panel, "Zip archive", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (option != JOptionPane.OK_OPTION || zipNameField.getText().trim().isEmpty()) { + requestFocusInActivePanel(); return; } + String zipName = zipNameField.getText().trim(); if (!zipName.toLowerCase().endsWith(".zip")) { zipName += ".zip"; } + String password = encryptCheckBox.isSelected() ? new String(passwordField.getPassword()) : null; + FilePanel targetPanel = (activePanel == leftPanel) ? rightPanel : leftPanel; File targetDir = targetPanel.getCurrentDirectory(); File targetZip = new File(targetDir, zipName); @@ -1974,6 +2006,7 @@ public class MainWindow extends JFrame { } final File finalTargetZip = targetZip; + final String finalPassword = password; final FilePanel sourcePanel = activePanel; int result = showConfirmWithBackground( "Zip %d items to:\n%s".formatted(selectedItems.size(), targetZip.getAbsolutePath()), @@ -1983,10 +2016,10 @@ public class MainWindow extends JFrame { boolean background = (result == 1); if (background) { addOperationToQueue("Zip", "Zip %d items to %s".formatted(selectedItems.size(), finalTargetZip.getName()), - (cb) -> FileOperations.zip(selectedItems, finalTargetZip, cb), () -> sourcePanel.unselectAll(), targetPanel); + (cb) -> FileOperations.zip(selectedItems, finalTargetZip, finalPassword, cb), () -> sourcePanel.unselectAll(), targetPanel); } else { performFileOperation((callback) -> { - FileOperations.zip(selectedItems, finalTargetZip, callback); + FileOperations.zip(selectedItems, finalTargetZip, finalPassword, callback); }, "Zipped into " + zipName, false, true, () -> sourcePanel.unselectAll(), targetPanel); } } else { @@ -3009,9 +3042,33 @@ public class MainWindow extends JFrame { final String[] result = new String[1]; try { SwingUtilities.invokeAndWait(() -> { - JPasswordField pf = new JPasswordField(); - int ok = JOptionPane.showConfirmDialog(progressDialog, pf, "Enter password for " + archiveName, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); - if (ok == JOptionPane.OK_OPTION) { + 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(progressDialog, "Password Required"); + + dialog.addWindowFocusListener(new WindowAdapter() { + @Override + public void windowGainedFocus(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()); } else { result[0] = null;