archive encrytpion added

This commit is contained in:
Radek Davidek 2026-03-18 15:05:39 +01:00
parent e191561e9c
commit 62162af00e
3 changed files with 143 additions and 71 deletions

View File

@ -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) {

View File

@ -1045,78 +1045,52 @@ public class FileOperations {
* Zip files/directories into a target zip file
*/
public static void zip(List<FileItem> 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<FileItem> items, File targetZipFile, String password, ProgressCallback callback) throws IOException {
List<FileItem> 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<PosixFilePermission> 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();
}
}

View File

@ -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;