/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.dialog.file; import com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.file.FileFactory; import com.mucommander.commons.file.util.FileSet; import com.mucommander.commons.file.util.PathUtils; import com.mucommander.commons.io.security.MuProvider; import com.mucommander.job.impl.CalculateChecksumJob; import com.mucommander.text.Translator; import com.mucommander.ui.action.ActionProperties; import com.mucommander.ui.action.impl.CalculateChecksumAction; import com.mucommander.ui.dialog.DialogToolkit; import com.mucommander.ui.layout.YBoxPanel; import com.mucommander.ui.main.MainFrame; import com.mucommander.ui.text.FilePathField; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.util.Comparator; import java.util.SortedSet; import java.util.TreeSet; /** * This dialog prepares a {@link com.mucommander.job.impl.CalculateChecksumJob} and lets the user choose a checksum * algorithm, and a destination for the checksum file. * * @author Maxence Bernard */ public class CalculateChecksumDialog extends JobDialog implements ActionListener, ItemListener { private JComboBox algorithmComboBox; private JRadioButton specificLocationRadioButton; private JTextField specificLocationTextField; private JButton okButton; /** An instance of all MessageDigest implementations */ private MessageDigest[] messageDigests; /** Default checksum algorithm (most commonly used) */ private final static String DEFAULT_ALGORITHM = "MD5"; /** Last algorithm used, saved after validation of this dialog */ private static String lastUsedAlgorithm = DEFAULT_ALGORITHM; /** Dialog size constraints */ private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0); static { // Register additional MessageDigest implementations provided by the muCommander API MuProvider.registerProvider(); } public CalculateChecksumDialog(MainFrame mainFrame, FileSet files) { super(mainFrame, ActionProperties.getActionLabel(CalculateChecksumAction.Descriptor.ACTION_ID), files); YBoxPanel mainPanel = new YBoxPanel(); // Retrieve all MessageDigest instances and sort them by alphabetical order of their algorithm // Create a TreeSet with a custom Comparator SortedSet<MessageDigest> algorithmSortedSet = new TreeSet<MessageDigest>(new Comparator<MessageDigest>() { public int compare(MessageDigest md1, MessageDigest md2) { return md1.getAlgorithm().compareTo(md2.getAlgorithm()); } }); // Add all MessageDigest to the TreeSet for(String algo : Security.getAlgorithms("MessageDigest")) { try { algorithmSortedSet.add(MessageDigest.getInstance(algo)); } catch(NoSuchAlgorithmException e) { // Should never happen and if it ever does, the digest will simply be discarded } } // Convert the sorted set into an array messageDigests = new MessageDigest[algorithmSortedSet.size()]; algorithmSortedSet.toArray(messageDigests); // Add the sorted list of algorithms to a combo box to let the user choose one algorithmComboBox = new JComboBox(); for (MessageDigest messageDigest : messageDigests) algorithmComboBox.addItem(messageDigest.getAlgorithm()); // Select the last used algorithm (if any), or the default algorithm algorithmComboBox.setSelectedItem(lastUsedAlgorithm); algorithmComboBox.addItemListener(this); FlowLayout flowLayout = new FlowLayout(FlowLayout.LEADING, 0, 0); JPanel tempPanel = new JPanel(flowLayout); tempPanel.add(new JLabel(Translator.get("calculate_checksum_dialog.checksum_algorithm")+" : ")); tempPanel.add(algorithmComboBox); mainPanel.add(tempPanel); mainPanel.addSpace(10); // Create the components that allow to choose where the checksum file should be created mainPanel.add(new JLabel(Translator.get("destination")+" :")); mainPanel.addSpace(5); JRadioButton tempLocationRadioButton = new JRadioButton(Translator.get("calculate_checksum_dialog.temporary_file"), true); mainPanel.add(tempLocationRadioButton); specificLocationRadioButton = new JRadioButton("", false); tempPanel = new JPanel(new BorderLayout()); tempPanel.add(specificLocationRadioButton, BorderLayout.WEST); specificLocationRadioButton.addItemListener(this); // Create a path field with auto-completion capabilities specificLocationTextField = new FilePathField(getChecksumFilename(lastUsedAlgorithm)); specificLocationTextField.setEnabled(false); tempPanel.add(specificLocationTextField, BorderLayout.CENTER); JPanel tempPanel2 = new JPanel(new BorderLayout()); tempPanel2.add(tempPanel, BorderLayout.NORTH); mainPanel.add(tempPanel2); ButtonGroup buttonGroup = new ButtonGroup(); buttonGroup.add(tempLocationRadioButton); buttonGroup.add(specificLocationRadioButton); // Create file details button and OK/cancel buttons and lay them out a single row JPanel fileDetailsPanel = createFileDetailsPanel(); okButton = new JButton(Translator.get("ok")); JButton cancelButton = new JButton(Translator.get("cancel")); mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel), DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this))); mainPanel.addSpace(3); mainPanel.add(fileDetailsPanel); // mainPanel.add(new HelpButtonPanel(new HelpButton(mainFrame, "CalculateChecksum"))); getContentPane().add(mainPanel); // Give initial keyboard focus to the 'Delete' button setInitialFocusComponent(algorithmComboBox); // Call dispose() when dialog is closed setDefaultCloseOperation(DISPOSE_ON_CLOSE); // Size dialog and show it to the screen setMinimumSize(MINIMUM_DIALOG_DIMENSION); setResizable(true); } /** * Returns the MessageDigest instance corresponding to the currently selected algorithm. * * @return the MessageDigest instance corresponding to the currently selected algorithm. */ private MessageDigest getSelectedMessageDigest() { return messageDigests[algorithmComboBox.getSelectedIndex()]; } /** * Returns a de-facto standard filename for the specified checksum algorithm, e.g. <code>MD5SUMS</code> for * <code>md5</code>. * * @param algorithm a checksum algorithm * @return a standard filename for the specified checksum algorithm */ private String getChecksumFilename(String algorithm) { // Adler32 -> ADLER32SUMS // CRC32 -> <filename>.sfv (needs special treatment) // MD2 -> MD2SUMS // MD4 -> MD4SUMS // MD5 -> MD5SUMS // SHA -> SHA1SUMS (needs special treatment) // SHA-256 -> SHA256SUMS // SHA-384 -> SHA384SUMS // SHA-512 -> SHA512SUMS algorithm = algorithm.toUpperCase(); if(algorithm.equals("SHA")) return "SHA1SUMS"; if(algorithm.equals("CRC32")) return (files.size()==1?files.elementAt(0):files.getBaseFolder()).getName()+".sfv"; return algorithm.replace("-", "")+"SUMS"; } /////////////////////////////////// // ActionListener implementation // /////////////////////////////////// public void actionPerformed(ActionEvent e) { // Start by disposing this dialog dispose(); if(e.getSource()==okButton) { try { MessageDigest digest = getSelectedMessageDigest(); String algorithm = digest.getAlgorithm(); AbstractFile checksumFile; // Resolve the destination checksum file if(specificLocationRadioButton.isSelected()) { // User-defined checksum file String enteredPath = specificLocationTextField.getText(); PathUtils.ResolvedDestination resolvedDest = PathUtils.resolveDestination(enteredPath, mainFrame.getActivePanel().getCurrentFolder()); // The path entered doesn't correspond to any existing folder if (resolvedDest==null) { showErrorDialog(Translator.get("invalid_path", enteredPath)); return; } if(resolvedDest.getDestinationType()==PathUtils.ResolvedDestination.EXISTING_FOLDER) checksumFile = resolvedDest.getDestinationFile().getDirectChild(getChecksumFilename(algorithm)); else checksumFile = resolvedDest.getDestinationFile(); } else { // Temporary file checksumFile = FileFactory.getTemporaryFile(getChecksumFilename(algorithm), true); } // Save the algorithm that was used for the next time this dialog is invoked lastUsedAlgorithm = algorithm; // Start processing files ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get("properties_dialog.calculating")); CalculateChecksumJob job = new CalculateChecksumJob(progressDialog, mainFrame, files, checksumFile, digest); progressDialog.start(job); } catch(IOException ex) { // Note: FileFactory.getTemporaryFile() should never throw an IOException showErrorDialog(Translator.get("invalid_path", specificLocationTextField.getText())); } } } ///////////////////////////////// // ItemListener implementation // ///////////////////////////////// public void itemStateChanged(ItemEvent e) { Object source = e.getSource(); if(source==specificLocationRadioButton) { // Enables/disables the text field when the corresponding radio button's selected state has changed. specificLocationTextField.setEnabled(specificLocationRadioButton.isSelected()); specificLocationTextField.requestFocus(); } else if(source==algorithmComboBox) { specificLocationTextField.setText(getChecksumFilename(getSelectedMessageDigest().getAlgorithm())); } } }