package com.kfmanager.ui; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.io.File; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Set; public class PropertiesDialog extends JDialog { private final File file; private final Path path; // Attributes Tab components private JCheckBox[][] permChecks; // [Owner, Group, Other][Read, Write, Execute] private JTextField octalField; private JTextField symbolicField; private JCheckBox suidCheck, sgidCheck, stickyCheck; private JCheckBox recursiveCheck; private JTextField ownerField, groupField; public PropertiesDialog(Window owner, File file) { super(owner, "Properties", ModalityType.APPLICATION_MODAL); this.file = file; this.path = file.toPath(); initComponents(); pack(); setMinimumSize(new Dimension(500, 550)); setLocationRelativeTo(owner); } private void initComponents() { setLayout(new BorderLayout()); JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("Properties", buildPropertiesPanel()); tabbedPane.addTab("Attributes", buildAttributesPanel()); add(tabbedPane, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); JButton okButton = new JButton("OK"); JButton cancelButton = new JButton("Cancel"); okButton.addActionListener(e -> { if (applyChanges()) { dispose(); } }); cancelButton.addActionListener(e -> dispose()); buttonPanel.add(okButton); buttonPanel.add(cancelButton); add(buttonPanel, BorderLayout.SOUTH); } private JPanel buildPropertiesPanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBorder(new EmptyBorder(20, 20, 20, 20)); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(5, 5, 5, 5); int row = 0; addInfoRow(panel, gbc, row++, "Name:", file.getName()); addInfoRow(panel, gbc, row++, "Path:", file.getAbsolutePath()); String sizeStr = file.isDirectory() ? "Directory" : formatSize(file.length()); addInfoRow(panel, gbc, row++, "Size:", sizeStr); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); addInfoRow(panel, gbc, row++, "Modified:", sdf.format(new Date(file.lastModified()))); return panel; } private JPanel buildAttributesPanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBorder(new EmptyBorder(15, 15, 15, 15)); GridBagConstraints gbc = new GridBagConstraints(); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(5, 5, 5, 5); int row = 0; // File name display JLabel nameLabel = new JLabel("File name"); gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0; panel.add(nameLabel, gbc); JLabel fileNameValue = new JLabel(file.getName()); gbc.gridx = 1; gbc.gridy = row; gbc.weightx = 1; gbc.gridwidth = 3; panel.add(fileNameValue, gbc); gbc.gridwidth = 1; row++; panel.add(new JSeparator(), new GridBagConstraints(0, row++, 4, 1, 1.0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(10, 0, 10, 0), 0, 0)); // Header: Read, Write, Execute gbc.gridy = row; gbc.weightx = 0; String[] titles = {"", "Read", "Write", "Execute"}; for (int i = 1; i < titles.length; i++) { gbc.gridx = i; panel.add(new JLabel(titles[i], SwingConstants.CENTER), gbc); } row++; // Rows: Owner, Group, Other String[] labels = {"Owner", "Group", "Other"}; permChecks = new JCheckBox[3][3]; Set perms = new HashSet<>(); String ownerName = ""; String groupName = ""; try { PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class); perms = attrs.permissions(); ownerName = attrs.owner().getName(); groupName = attrs.group().getName(); } catch (Exception ignore) { // Fallback for non-posix systems or permission issues if (file.canRead()) perms.add(PosixFilePermission.OWNER_READ); if (file.canWrite()) perms.add(PosixFilePermission.OWNER_WRITE); if (file.canExecute()) perms.add(PosixFilePermission.OWNER_EXECUTE); } for (int i = 0; i < 3; i++) { gbc.gridx = 0; gbc.gridy = row; panel.add(new JLabel(labels[i]), gbc); for (int j = 0; j < 3; j++) { permChecks[i][j] = new JCheckBox(); permChecks[i][j].setHorizontalAlignment(SwingConstants.CENTER); gbc.gridx = j + 1; panel.add(permChecks[i][j], gbc); // Set initial state permChecks[i][j].setSelected(hasPermission(perms, i, j)); permChecks[i][j].addActionListener(e -> updateOctalAndSymbolic()); } row++; } panel.add(new JSeparator(), new GridBagConstraints(0, row++, 4, 1, 1.0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(10, 0, 10, 0), 0, 0)); // Special Bits JPanel bitsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0)); bitsPanel.add(new JLabel("Bits:")); suidCheck = new JCheckBox("SUID"); sgidCheck = new JCheckBox("SGID"); stickyCheck = new JCheckBox("Sticky"); suidCheck.setEnabled(false); // Advanced bits usually require more than just Java basic API or are rare sgidCheck.setEnabled(false); stickyCheck.setEnabled(false); bitsPanel.add(suidCheck); bitsPanel.add(sgidCheck); bitsPanel.add(stickyCheck); gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 4; panel.add(bitsPanel, gbc); gbc.gridwidth = 1; // Octal and Symbolic gbc.gridy = row; gbc.gridx = 0; panel.add(new JLabel("Octal:"), gbc); octalField = new JTextField(5); gbc.gridx = 1; panel.add(octalField, gbc); gbc.gridx = 2; panel.add(new JLabel("Text:"), gbc); symbolicField = new JTextField(12); symbolicField.setEditable(false); gbc.gridx = 3; panel.add(symbolicField, gbc); row++; // Owner/Group Section JPanel ownerGroupPanel = new JPanel(new GridBagLayout()); ownerGroupPanel.setBorder(BorderFactory.createTitledBorder("Owner")); GridBagConstraints ogbc = new GridBagConstraints(); ogbc.fill = GridBagConstraints.HORIZONTAL; ogbc.insets = new Insets(2, 5, 2, 5); ogbc.gridx = 0; ogbc.gridy = 0; ownerGroupPanel.add(new JLabel("Owner"), ogbc); ownerField = new JTextField(ownerName); ownerField.setEditable(false); ogbc.gridx = 1; ogbc.weightx = 1.0; ownerGroupPanel.add(ownerField, ogbc); ogbc.gridx = 0; ogbc.gridy = 1; ownerGroupPanel.add(new JLabel("Group"), ogbc); groupField = new JTextField(groupName); groupField.setEditable(false); ogbc.gridx = 1; ownerGroupPanel.add(groupField, ogbc); gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 4; panel.add(ownerGroupPanel, gbc); gbc.gridwidth = 1; // Recursive recursiveCheck = new JCheckBox("Recursive"); recursiveCheck.setEnabled(file.isDirectory()); gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 4; panel.add(recursiveCheck, gbc); updateOctalAndSymbolic(); // Listener for octal field to update checkboxes octalField.addActionListener(e -> updateFromOctal()); return panel; } private boolean hasPermission(Set perms, int userIdx, int permIdx) { PosixFilePermission p = getPermission(userIdx, permIdx); return p != null && perms.contains(p); } private PosixFilePermission getPermission(int userIdx, int permIdx) { if (userIdx == 0) { // Owner if (permIdx == 0) return PosixFilePermission.OWNER_READ; if (permIdx == 1) return PosixFilePermission.OWNER_WRITE; if (permIdx == 2) return PosixFilePermission.OWNER_EXECUTE; } else if (userIdx == 1) { // Group if (permIdx == 0) return PosixFilePermission.GROUP_READ; if (permIdx == 1) return PosixFilePermission.GROUP_WRITE; if (permIdx == 2) return PosixFilePermission.GROUP_EXECUTE; } else if (userIdx == 2) { // Other if (permIdx == 0) return PosixFilePermission.OTHERS_READ; if (permIdx == 1) return PosixFilePermission.OTHERS_WRITE; if (permIdx == 2) return PosixFilePermission.OTHERS_EXECUTE; } return null; } private void updateOctalAndSymbolic() { int octal = 0; if (permChecks[0][0].isSelected()) octal += 0400; if (permChecks[0][1].isSelected()) octal += 0200; if (permChecks[0][2].isSelected()) octal += 0100; if (permChecks[1][0].isSelected()) octal += 0040; if (permChecks[1][1].isSelected()) octal += 0020; if (permChecks[1][2].isSelected()) octal += 0010; if (permChecks[2][0].isSelected()) octal += 0004; if (permChecks[2][1].isSelected()) octal += 0002; if (permChecks[2][2].isSelected()) octal += 0001; octalField.setText(String.format("%03o", octal)); StringBuilder sb = new StringBuilder(); sb.append(file.isDirectory() ? 'd' : '-'); sb.append(permChecks[0][0].isSelected() ? 'r' : '-'); sb.append(permChecks[0][1].isSelected() ? 'w' : '-'); sb.append(permChecks[0][2].isSelected() ? 'x' : '-'); sb.append(permChecks[1][0].isSelected() ? 'r' : '-'); sb.append(permChecks[1][1].isSelected() ? 'w' : '-'); sb.append(permChecks[1][2].isSelected() ? 'x' : '-'); sb.append(permChecks[2][0].isSelected() ? 'r' : '-'); sb.append(permChecks[2][1].isSelected() ? 'w' : '-'); sb.append(permChecks[2][2].isSelected() ? 'x' : '-'); symbolicField.setText(sb.toString()); } private void updateFromOctal() { try { int val = Integer.parseInt(octalField.getText(), 8); permChecks[0][0].setSelected((val & 0400) != 0); permChecks[0][1].setSelected((val & 0200) != 0); permChecks[0][2].setSelected((val & 0100) != 0); permChecks[1][0].setSelected((val & 0040) != 0); permChecks[1][1].setSelected((val & 0020) != 0); permChecks[1][2].setSelected((val & 0010) != 0); permChecks[2][0].setSelected((val & 0004) != 0); permChecks[2][1].setSelected((val & 0002) != 0); permChecks[2][2].setSelected((val & 0001) != 0); updateOctalAndSymbolic(); } catch (Exception ignore) {} } private boolean applyChanges() { Set perms = new HashSet<>(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (permChecks[i][j].isSelected()) { perms.add(getPermission(i, j)); } } } try { applyPermissions(path, perms, recursiveCheck.isSelected()); return true; } catch (Exception ex) { JOptionPane.showMessageDialog(this, "Failed to apply permissions: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); return false; } } private void applyPermissions(Path p, Set perms, boolean recursive) throws IOException { try { Files.setPosixFilePermissions(p, perms); } catch (UnsupportedOperationException e) { // Fallback for non-posix filesystems File f = p.toFile(); f.setReadable(perms.contains(PosixFilePermission.OWNER_READ)); f.setWritable(perms.contains(PosixFilePermission.OWNER_WRITE)); f.setExecutable(perms.contains(PosixFilePermission.OWNER_EXECUTE)); } if (recursive && Files.isDirectory(p)) { try (DirectoryStream stream = Files.newDirectoryStream(p)) { for (Path entry : stream) { applyPermissions(entry, perms, true); } } } } private void addInfoRow(JPanel panel, GridBagConstraints gbc, int row, String label, String value) { gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0; panel.add(new JLabel(label), gbc); gbc.gridx = 1; gbc.weightx = 1.0; JTextField textField = new JTextField(value); textField.setEditable(false); textField.setBorder(null); textField.setOpaque(false); panel.add(textField, gbc); } private String formatSize(long bytes) { if (bytes < 1024) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(1024)); char pre = "KMGTPE".charAt(exp - 1); return String.format("%.1f %cB (%d bytes)", bytes / Math.pow(1024, exp), pre, bytes); } }