/* * Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.tulskiy.musique.gui.dialogs; import com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel; import com.tulskiy.musique.gui.components.GroupTable; import com.tulskiy.musique.playlist.Playlist; import com.tulskiy.musique.playlist.Track; import com.tulskiy.musique.playlist.formatting.Parser; import com.tulskiy.musique.playlist.formatting.tokens.Expression; import com.tulskiy.musique.system.Application; import com.tulskiy.musique.system.configuration.Configuration; import com.tulskiy.musique.system.configuration.FileOperationsConfiguration; import com.tulskiy.musique.util.FileUtils; import com.tulskiy.musique.util.Util; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.util.*; import java.util.List; /** * Author: Denis Tulskiy * Date: Aug 1, 2010 */ public class FileOperations extends JDialog { private static final ArrayList<String> DEFAULT_PATTERNS = new ArrayList<String>(Arrays.asList( "%fileName%", "[%artist% - ]%title%", "[%trackNumber% - ]%title%", "[%year% - ]%album%/%fileName%") ); private JComboBox namePattern; private PathChooser folder; private DefaultTableModel previewModel; private HashMap<Track, File> paths = new LinkedHashMap<Track, File>(); private Application app = Application.getInstance(); private Configuration config = app.getConfiguration(); public enum Operation { Copy, Move, Rename } private Operation mode; private List<Track> tracks; public FileOperations(final JComponent owner, Operation mode, List<Track> tracks) { super(SwingUtilities.windowForComponent(owner), "File Operations"); this.mode = mode; this.tracks = tracks; setDefaultCloseOperation(DISPOSE_ON_CLOSE); JPanel top = new JPanel(new GridLayout(3, 2, 5, 5)); top.setBorder(BorderFactory.createTitledBorder("Options")); final JComboBox op = new JComboBox(Operation.values()); top.add(new JLabel("Operation")); top.add(op); folder = new PathChooser(""); folder.addPropertyChangeListener("path", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { updatePreview(); } }); top.add(new JLabel("Destination")); top.add(folder); final List<String> patterns = FileOperationsConfiguration.getPatterns(DEFAULT_PATTERNS); namePattern = new JComboBox(patterns.toArray()); namePattern.setEditable(true); top.add(new JLabel("File name pattern")); top.add(namePattern); JPanel main = new JPanel(new BorderLayout()); main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); main.add(top, BorderLayout.NORTH); GroupTable preview = new GroupTable() { public Component prepareRenderer(final TableCellRenderer renderer, final int row, final int column) { final Component prepareRenderer = super .prepareRenderer(renderer, row, column); final TableColumn tableColumn = getColumnModel().getColumn(column); tableColumn.setPreferredWidth(Math.max( prepareRenderer.getPreferredSize().width, tableColumn.getPreferredWidth())); return prepareRenderer; } }; previewModel = new DefaultTableModel(new Object[]{"Source", "Destination"}, 10) { @Override public boolean isCellEditable(int row, int column) { return false; } }; preview.setModel(previewModel); preview.setFont(preview.getFont().deriveFont(11f)); preview.setGridColor(Color.lightGray); preview.setShowVerticalLines(true); preview.setIntercellSpacing(new Dimension(1, 1)); JScrollPane scroll = new JScrollPane(preview); scroll.setBorder(BorderFactory.createTitledBorder("Preview")); main.add(scroll, BorderLayout.CENTER); op.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { FileOperations.this.mode = (Operation) op.getSelectedItem(); if (FileOperations.this.mode == Operation.Rename) { folder.setEnabled(false); } else { folder.setEnabled(true); } updatePreview(); } }); op.setSelectedItem(mode); JTextComponent textComp = (JTextComponent) namePattern.getEditor().getEditorComponent(); textComp.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { updatePreview(); } @Override public void removeUpdate(DocumentEvent e) { updatePreview(); } @Override public void changedUpdate(DocumentEvent e) { updatePreview(); } }); namePattern.setSelectedItem(config.getString("fileOperations.selectedPattern", patterns.get(0))); folder.setPath(config.getString("fileOperations.path", "")); Box buttons = Box.createHorizontalBox(); buttons.add(Box.createHorizontalGlue()); JButton ok = new JButton(" Start "); buttons.add(ok); ok.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { config.setString("fileOperations.path", folder.getPath()); String selectedItem = (String) namePattern.getSelectedItem(); config.setString("fileOperations.selectedPattern", selectedItem); if (!patterns.contains(selectedItem)) patterns.add(selectedItem); FileOperationsConfiguration.setPatterns(patterns); setVisible(false); dispose(); ProgressDialog progress = new ProgressDialog(owner, "File Operations"); progress.show(new CopyTask()); } }); buttons.add(Box.createHorizontalStrut(3)); JButton cancel = new JButton("Cancel"); cancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setVisible(false); dispose(); } }); buttons.add(cancel); buttons.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); main.add(buttons, BorderLayout.PAGE_END); updatePreview(); setContentPane(main); setSize(700, 500); setLocationRelativeTo(null); } public void updatePreview() { String text = (String) namePattern.getEditor().getItem(); previewModel.setRowCount(0); paths.clear(); if (!Util.isEmpty(text)) { text = text.replaceAll("%([\\w]+)%", "\\$escape($0)"); Expression expression = Parser.parse(text); for (Track track : tracks) { Object key; if (track.getTrackData().isFile() && !track.getTrackData().isCue()) { File file = track.getTrackData().getFile(); key = file.getName(); StringBuilder sb = new StringBuilder(); Object o = expression.eval(track); if (o == null) continue; sb.append(o); sb.append(".").append(Util.getFileExt(file)); File output = null; if (mode == Operation.Rename) { output = new File(file.getParent(), sb.toString()); } else { File path = new File(folder.getPath()); if (path.exists() && path.isDirectory()) output = new File(folder.getPath(), sb.toString()); } if (output != null) { previewModel.addRow(new Object[]{key, output.getAbsolutePath()}); paths.put(track, output); } } } } } class CopyTask extends Task { private File src; private File dest; private long processed; private long totalSize; private boolean abort = false; @Override public String getStatus() { StringBuilder sb = new StringBuilder(); switch (mode) { case Copy: sb.append("Copying: "); break; case Move: sb.append("Moving: "); break; case Rename: sb.append("Renaming: "); } sb.append(src).append("\n"); sb.append("Destination: ").append(dest); return sb.toString(); } @Override public void abort() { abort = true; } @SuppressWarnings({"ResultOfMethodCallIgnored"}) @Override public void start() { totalSize = 0; for (Track track : paths.keySet()) { totalSize += track.getTrackData().getFile().length(); } ArrayList<String> log = new ArrayList<String>(); processed = 0; for (Map.Entry<Track, File> entry : paths.entrySet()) { if (abort) break; src = entry.getKey().getTrackData().getFile(); dest = entry.getValue(); dest.getParentFile().mkdirs(); if (mode == Operation.Rename) { if (!src.renameTo(dest)) log.add("Failed to rename " + src + " to " + dest); processed += src.length(); } else { if (mode == Operation.Move) { //try to rename first if (src.renameTo(dest)) { processed += src.length(); entry.getKey().getTrackData().setLocation(dest.toURI().toString()); // TODO "delete yes/no" decision to be remembered FileUtils.deleteEmptyParentFolders(src, true); continue; } } //now copy try { if (src.canRead() || dest.canWrite()) { FileChannel from = new FileInputStream(src).getChannel(); FileChannel to = new FileOutputStream(dest).getChannel(); long transferred = 0; long length = from.size(); int chunkSize = 10000000; while (transferred < length) { if (abort) break; long len = from.transferTo( transferred, chunkSize, to); transferred += len; processed += len; } from.close(); to.close(); } else { log.add("Not enough permissions to copy/move " + src + " to " + dest); } } catch (ClosedChannelException ignore) { } catch (IOException e) { e.printStackTrace(); } if (abort) { dest.delete(); } else if (mode == Operation.Move) { if (src.delete()) { // TODO "delete yes/no" decision to be remembered FileUtils.deleteEmptyParentFolders(src, true); } else { log.add("Failed to remove " + src); } } } if ((mode == Operation.Move || mode == Operation.Rename) && dest.exists()) { entry.getKey().getTrackData().setLocation(dest.toURI().toString()); } } } @Override public boolean isIndeterminate() { return false; } @Override public float getProgress() { return (float) processed / totalSize; } } public static void main(String[] args) { Application app = Application.getInstance(); app.load(); try { UIManager.setLookAndFeel(new NimbusLookAndFeel()); } catch (UnsupportedLookAndFeelException e) { e.printStackTrace(); } Playlist pl = new Playlist(); JLabel label = new JLabel(); pl.insertItem("/windows/Users/tulskiy/Music/Avril Lavigne", 0, true, null); FileOperations dialog = new FileOperations(label, Operation.Rename, pl); dialog.setVisible(true); } }