/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program 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 2 * of the License, or any later version. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.ui.actions; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JOptionPane; import javax.swing.SwingWorker; import org.jajuk.base.File; import org.jajuk.base.Track; import org.jajuk.base.TrackComparator; import org.jajuk.base.TrackComparator.TrackComparatorType; import org.jajuk.base.TrackManager; import org.jajuk.ui.windows.JajukMainWindow; import org.jajuk.ui.wizard.DuplicateTracksDialog; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.IconLoader; import org.jajuk.util.JajukIcons; import org.jajuk.util.Messages; import org.jajuk.util.UtilGUI; /** * . */ public class FindDuplicateTracksAction extends JajukAction { /** Generated serialVersionUID. */ private static final long serialVersionUID = 1L; /** Result : a list of dups files for a given track. */ List<List<File>> duplicateTracksList; /** Temporary storage during dups detection. */ private Map<String, Set<File>> mapTrackDups; /** Track comparator. */ TrackComparator comparator = new TrackComparator(TrackComparatorType.ALMOST_IDENTICAL); /** * Instantiates a new find duplicate tracks action. */ FindDuplicateTracksAction() { super(Messages.getString("FindDuplicateTracksAction.2"), IconLoader.getIcon(JajukIcons.SEARCH), true); setShortDescription(Messages.getString("FindDuplicateTracksAction.2")); } /** * Add a dup for a given track. * * @param track * @param files list of files */ private void addDup(Track track, List<File> files) { // Ignore case where thy are none ready files if (files.size() > 0) { String key = comparator.buildIdenticalTestFootprint(track).toLowerCase(); Set<File> dups = mapTrackDups.get(key); if (dups == null) { // We sort files by path because we don't want to allow user to drop files from different directories dups = new TreeSet<File>(); mapTrackDups.put(key, dups); } dups.addAll(files); } } /* * Return the next track relative to current position or null if it is the last track * @return the next track relative to current position or null if it is the last track */ /** * Gets the next track. * * @param tracks * @param index * * @return the next track */ private Track getNextTrack(List<Track> tracks, int index) { Track next = null; if (index < tracks.size() - 1) { next = tracks.get(index + 1); } return next; } /** * Return either all or only mounted files for given track * according to OPTIONS_HIDE_UNMOUNTED option. * * @param track * * @return either all or only mounted files for given track */ private List<File> getFiles(Track track) { if (Conf.getBoolean(Const.CONF_OPTIONS_HIDE_UNMOUNTED)) { return track.getReadyFiles(); } else { return track.getFiles(); } } /** * Create the dups list. */ void populateDups() { duplicateTracksList = new ArrayList<List<File>>(); // Use a tree map so footprints are sorted mapTrackDups = new TreeMap<String, Set<File>>(); List<Track> tracks = TrackManager.getInstance().getTracks(); // For finding duplicate files, we don't just rely on the number of files associated with // a track (>1), we also find almost-identical tracks, ie based on album name, not its ID // because then, we can't detect identical files located in several directories with a // different set of files (because track uses album id in its hashcode and album id uses CDDB discid // computed by jajuk based on the duration of all files in a given directory) // Sort using the ALMOST-IDENTICAL Collections.sort(tracks, comparator); int index = 0; while (index <= tracks.size() - 1) { Track track = tracks.get(index); Track next = getNextTrack(tracks, index); // 1- Find dups files for the same track if (getFiles(track).size() > 1) { addDup(track, getFiles(track)); } // 2- Compare each track to find adjacent duplicates (different tracks) if (next != null && comparator.compare(track, next) == 0) { addDup(track, getFiles(track)); addDup(next, getFiles(next)); } index++; } // Build final list (note that it is already sorted by track, mapTrackDups is a TreeMap) for (String footprint : mapTrackDups.keySet()) { Set<File> dups = mapTrackDups.get(footprint); // dups can be 1 in fuzzy search if track1 ~= track2 and track1 files are mounted and not the tracks2's ones if (dups.size() > 1) { duplicateTracksList.add(new ArrayList<File>(dups)); } } } /* * (non-Javadoc) * * @see org.jajuk.ui.actions.JajukAction#perform(java.awt.event.ActionEvent) */ @Override public void perform(final ActionEvent evt) throws Exception { UtilGUI.waiting(); SwingWorker<Void, Void> sw = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { populateDups(); return null; } @Override public void done() { try { if (duplicateTracksList.size() == 0) { Messages.showInfoMessage(Messages.getString("FindDuplicateTracksAction.0")); } else { final JOptionPane optionPane = UtilGUI.getNarrowOptionPane(100); final JDialog duplicateFiles = optionPane.createDialog(null, Messages.getString("FindDuplicateTracksAction.3")); duplicateFiles.setResizable(true); JButton jbClose = new JButton(Messages.getString("Close")); jbClose.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { duplicateFiles.dispose(); } }); // Create and set up the content pane. JComponent newContentPane = new DuplicateTracksDialog(duplicateTracksList, jbClose); newContentPane.setOpaque(true); UtilGUI.setEscapeKeyboardAction(duplicateFiles, newContentPane); duplicateFiles.setContentPane(newContentPane); // Display the window. duplicateFiles.setSize(800, 600); duplicateFiles.setLocationRelativeTo(JajukMainWindow.getInstance()); duplicateFiles.setVisible(true); } } finally { UtilGUI.stopWaiting(); } } }; sw.execute(); } }