Package entagged.tageditor.tools.renaming

Source Code of entagged.tageditor.tools.renaming.FileRenamer

/*
*  ********************************************************************   **
*  Copyright notice                                                       **
*  **                                     **
*  (c) 2003 Entagged Developpement Team                           **
*  http://www.sourceforge.net/projects/entagged                           **
*  **                                     **
*  All rights reserved                                                    **
*  **                                     **
*  This script is part of the Entagged project. The Entagged          **
*  project 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      **
*  (at your option) any later version.                                    **
*  **                                     **
*  The GNU General Public License can be found at                         **
*  http://www.gnu.org/copyleft/gpl.html.                                  **
*  **                                     **
*  This copyright notice MUST APPEAR in all copies of the file!           **
*  ********************************************************************
*/

package entagged.tageditor.tools.renaming;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.regex.Pattern;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import entagged.listing.ListingProcessor;
import entagged.listing.gui.ListingProgressDialog;
import entagged.tageditor.TagEditorFrame;
import entagged.tageditor.resources.LangageManager;
import entagged.tageditor.tools.renaming.data.DirectoryDescriptor;
import entagged.tageditor.tools.renaming.data.DirectoryPruneStructure;
import entagged.tageditor.tools.renaming.data.FileDescriptor;
import entagged.tageditor.tools.renaming.data.ProcessingResult;
import entagged.tageditor.tools.renaming.data.RenameConfiguration;
import entagged.tageditor.tools.renaming.data.stat.properties.CanNotWriteProperty;
import entagged.tageditor.tools.renaming.data.stat.properties.ContainsWarningsProperty;
import entagged.tageditor.tools.renaming.data.stat.properties.DestinationExistsProperty;
import entagged.tageditor.tools.renaming.data.stat.properties.DuplicateErrorProperty;
import entagged.tageditor.tools.renaming.data.stat.properties.NoChangeProperty;
import entagged.tageditor.tools.renaming.gui.InspectionControl;
import entagged.tageditor.tools.renaming.model.DefaultTreeModel;
import entagged.tageditor.tools.renaming.model.TargetTreeFilter;
import entagged.tageditor.tools.renaming.pattern.DirectoryPattern;
import entagged.tageditor.tools.renaming.pattern.FilePattern;
import entagged.tageditor.tools.renaming.processing.FileProcessor;
import entagged.tageditor.tools.stringtransform.TransformSet;

/**
* This class is meant for performing file movement or file renaming.<br>
* The {@link #run()} method will start the scanning of the given selection,
* present the results, and rename and moves the audiofiles.<br>
* <b>Consider:</b> This class will show dialogs, its not just processing.
*
* @author Christian Laireiter
*/
public final class FileRenamer implements Runnable {

  /**
   * This constant represents the placeholder for the album of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String ALBUM_MASK = "<album>";

  /**
   * This constant represents the placeholder for the artist of an audio tag.<br>
   * It is used to define patterns.
   */

  public static final String ARTIST_MASK = "<artist>";

  /**
   * This constant represents the placeholder for the bitrate of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String BITRATE_MASK = "<bitrate>";

  /**
   * This constant represents the placeholder for the comment of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String COMMENT_MASK = "<comment>";

  /**
   * This constant represents the placeholder for the genre of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String GENRE_MASK = "<genre>";

  /**
   * This field contains a {@link Pattern}object which is compiled to
   * identify a set of invalid characters for directory specification. <br>
   * One can easily replace all defined characters of a string by doing the
   * following: <br>
   *
   * <pre>
   * String path = &quot;C:\\temp\\Hello%World&quot;;
   * Matcher matcher = PATTERN_INVALID_DIRECTORY_CHARS.matcher(path);
   * path = matcher.replaceAll(&quot;-&quot;);
   * </pre>
   *
   * <br>
   * No Path will be "C:\temp\Hello-World"; <br>
   * <br>
   * Using this will speed up multiple replacements.
   */
  public final static Pattern PATTERN_INVALID_DIRECTORY_CHARS = Pattern
      .compile("(\\:|\\?|\\*|\\\"|\\'|\\<|\\>|\\||\\!|\\%|\\Q"
          + File.separatorChar + "\\E|\\Q" + File.pathSeparatorChar
          + "\\E)");

  /**
   * Like {@link #PATTERN_INVALID_DIRECTORY_CHARS}this field defines the same
   * chars plus path separators ("\" and "/") and
   * System.getProperty("path.separator") <br>
   */
  public final static Pattern PATTERN_INVALID_FILE_CHARS = Pattern
      .compile("(\\:|\\?|\\*|\\\"|\\'|\\<|\\>|\\||\\!|\\%|\\\\|\\/|\\Q"
          + File.separatorChar + "\\E|\\Q" + File.pathSeparatorChar
          + "\\E)");

  /**
   * This constant represents the placeholder for the title of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String TITLE_MASK = "<title>";

  /**
   * This constant represents the placeholder for the track of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String TRACK_MASK = "<track>";

  /**
   * This constant represents the placeholder for the year of an audio tag.<br>
   * It is used to define patterns.
   */
  public static final String YEAR_MASK = "<year>";

  /**
   * If this is set to <code>true</code>, the user aborted the operation.
   */
  private boolean aborted = false;

  /**
   * This field recieves a parent for the preview window.
   */
  private JFrame parentFrame = null;

  /**
   * Stores the result of the processing.<br>
   */
  private ProcessingResult processingResult;

  /**
   * This field will be assigned at each call of {@link #run()}. <br>
   * The methode {@link #renameFile(FileDescriptor)} will fill it with those
   * directories, which are the source of a handeled file and not a target of
   * any other (this is easy due to a Flag).
   */
  private DirectoryPruneStructure pruneStruct;

  /**
   * This field stores the configuration of the current operation.<br>
   */
  private RenameConfiguration renameConfig;

  /**
   * Creates an instance of the file renamin operation.<br>
   *
   * @param selection
   *            The files or directories which should be processed.
   * @param directoryPattern
   *            The directorypattern for the operation. May be
   *            <code>null</code> to indicate that no movement should be
   *            performed.<br>
   * @param filePattern
   *            The filenamepattern for the operation. May be
   *            <code>null</code> to indicate that the filename should be
   *            preserved.<br>
   * @param transformSet
   *            The set of transformation which will be used.
   */
  public FileRenamer(File[] selection, String directoryPattern,
      String filePattern, TransformSet transformSet) {
    assert selection != null;
    this.renameConfig = new RenameConfiguration(selection,
        new DirectoryPattern(directoryPattern), new FilePattern(
            filePattern), transformSet);
    /*
     * Applay transfomations
     */
    this.renameConfig.getDirectoryPattern().setTransformSet(transformSet);
    this.renameConfig.getFilePattern().setTransformSet(transformSet);
    this.processingResult = new ProcessingResult();
  }

  /**
   * Creates an instance of the file renamin operation.<br>
   *
   * @param previewParent
   *            A frame, which becomes parent of the prieview winodw.
   * @param selection
   *            The files or directories which should be processed.
   * @param directoryPattern
   *            The directorypattern for the operation. May be
   *            <code>null</code> to indicate that no movement should be
   *            performed.<br>
   * @param filePattern
   *            The filenamepattern for the operation. May be
   *            <code>null</code> to indicate that the filename should be
   *            preserved.<br>
   * @param transformSet
   *            The set of transformation which will be used.
   */
  public FileRenamer(JFrame previewParent, File[] selection,
      String directoryPattern, String filePattern,
      TransformSet transformSet) {
    this(selection, directoryPattern, filePattern, transformSet);
    this.parentFrame = previewParent;
  }

  /**
   * This method will copy the <code>file</code> to the given
   * <code>targetFile</code>
   *
   * @param file
   *            The file to be copied.
   * @param targetFile
   *            The destination.
   */
  private void copyFile(File file, File targetFile) {
    try {
      BufferedInputStream bis = new BufferedInputStream(
          new FileInputStream(file));
      BufferedOutputStream bos = new BufferedOutputStream(
          new FileOutputStream(targetFile));
      byte[] tmp = new byte[8192];
      int read = -1;
      while ((read = bis.read(tmp)) > 0) {
        bos.write(tmp, 0, read);
      }
      bis.close();
      bos.close();
    } catch (Exception e) {
      if (!targetFile.delete()) {
        System.err.println("Ups, created copy cant be deleted ("
            + targetFile.getAbsolutePath() + ")");
      }
    }
  }

  /**
   * This method returns the configuration object.
   *
   * @return Configuration of renaming.
   */
  public RenameConfiguration getConfig() {
    return this.renameConfig;
  }

  /**
   * @return Returns the parentFrame.
   */
  public JFrame getParentFrame() {
    return this.parentFrame;
  }

  /**
   * Returns the result structure.
   *
   * @return Result structure.
   */
  public ProcessingResult getResult() {
    return this.processingResult;
  }

  /**
   * This method reads the children of the current parent out of the treemodel
   * and renames them (if they represent files).
   *
   * @param lpd
   *            The progress dialog.
   * @param treemodel
   *            The treemodel.
   * @param currentParent
   *            The current parent object (will be a directory).
   */
  private void handleChildren(ListingProgressDialog lpd,
      DefaultTreeModel treemodel, Object currentParent) {
    int childCount = treemodel.getChildCount(currentParent);
    lpd.directoryChanged(childCount, currentParent.toString());
    for (int i = 0; i < childCount && !this.aborted; i++) {
      this.aborted = lpd.abort();
      Object currentChild = treemodel.getChild(currentParent, i);
      if (!treemodel.isLeaf(currentChild)) {
        handleChildren(lpd, treemodel, currentChild);
        lpd.directoryChanged(childCount, currentParent.toString());
      } else {
        lpd.fileChanged(i, currentChild.toString());
        // now we have got a file, its getting interesting.
        if (currentChild instanceof FileDescriptor) {
          renameFile((FileDescriptor) currentChild);
        }
      }
    }
  }

  /**
   * Returns <code>true</code> if the user aborted the operation.
   *
   * @return <code>true</code> if renaming was aborted.
   */
  public boolean isAborted() {
    return this.aborted;
  }

  /**
   * Will prune the statistics of the given descriptors.<br>
   *
   * @param fileSystemRoots
   *            directories to process.
   */
  private void refreshStatistics(DirectoryDescriptor[] fileSystemRoots) {
    for (int i = 0; i < fileSystemRoots.length; i++) {
      fileSystemRoots[i]
          .refreshStat(DestinationExistsProperty.PROPERTY_NAME);
      fileSystemRoots[i]
          .refreshStat(DuplicateErrorProperty.PROPERTY_NAME);
      fileSystemRoots[i]
          .refreshStat(ContainsWarningsProperty.PROPERTY_NAME);
    }
  }

  /**
   * Renames the given file.<br>
   * <b>Consider:</b> {@link #pruneStruct} must not be <code>null</code>.
   *
   * @param descriptor
   *            The file descriptor, which represents the file to rename.
   */
  private void renameFile(FileDescriptor descriptor) {
    if (descriptor.getStatistic().getProperty(
        NoChangeProperty.PROPERTY_NAME) > 0) {
      // The file doesn't need to be renamed.
      return;
    }
    DirectoryDescriptor targetDir = descriptor.getTargetDirectory();
    if (targetDir != null) {
      File targetDirFile = new File(targetDir.getPath());
      if (!targetDirFile.exists()) {
        if (!targetDirFile.mkdirs()) {
          throw new RuntimeException("Can't create "
              + targetDir.getPath());
        }
      }
      /*
       * Now check one more time if the target exists.
       */
      File targetFile = new File(descriptor.getTargetDirectory()
          .getPath()
          + File.separator + descriptor.getTargetName());
      if (targetFile.exists()) {
        throw new RuntimeException("Target file already exists \n"
            + targetFile.getAbsolutePath());
      }
      /*
       * Add the source directory for pruning. One could think to not add
       * the current directory, if it is marked as a target. But it may
       * happen, that a directory is marked as target, since a file would
       * be placed there if the file could be moved. If the file cannot be
       * moved the directory descriptor still exists or is marked as
       * target, since one could use the option to copy those files. So
       * add it and exclude it
       */
      pruneStruct.addDirectory(descriptor.getSourceDirectory().getPath());
      if (descriptor.getStatistic().getProperty(
          CanNotWriteProperty.PROPERTY_NAME) > 0) {
        // Here we must copy, but only if it was requested by user.
        // Request ist normally not necessary, however if the filter
        // malfunctions...
        if (renameConfig.isCopyUnmodifiableFiles()) {
          copyFile(new File(descriptor.getPath()), targetFile);
          // Tell the pruning, that the source directory must not
          // be considered for deletion, since this file resides.
          pruneStruct.excludeDirectory(descriptor.getPath());
        }
      } else {
        File sourceFile = new File(descriptor.getPath());
        if (!sourceFile.renameTo(targetFile)) {
          // For time of programming an exception is thrown. It
          // may be possible that another one than me would remove
          // the throw statement. In this case let the pruning
          // facility
          // know, that the source directory won't be empty
          pruneStruct.excludeDirectory(descriptor
              .getSourceDirectory().getPath());
          throw new RuntimeException("can't rename (\n"
              + sourceFile.getAbsolutePath() + "\nto\n"
              + targetFile.getAbsolutePath() + ")");
        }
      }
    }
  }

  /**
   * This method traverses the tree structure using the
   * {@link entagged.tageditor.tools.renaming.model.DefaultTreeModel} in
   * conjunction with
   * {@link entagged.tageditor.tools.renaming.model.TargetTreeFilter}.<br>
   * This is what the user has seen and confirmed.
   */
  private void renameFiles() {
    DefaultTreeModel treemodel = new DefaultTreeModel(processingResult
        .getFileSystemRoots(), new TargetTreeFilter(getConfig()),
        "arbitrary");
    Object currentParent = treemodel.getRoot();
    /*
     * Create gui
     */
    final ListingProgressDialog lpd = new ListingProgressDialog(parentFrame);
    lpd.setModal(true);
    lpd.setLocationRelativeTo(null);
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        // Blocks entagged parent frame if given.
        lpd.setVisible(true);
      }

    });
    try {
      handleChildren(lpd, treemodel, currentParent);
      lpd.appendMessage(LangageManager.getProperty("tagrename.prune"));
      pruneStruct.prune();
    } catch (RuntimeException re) {
      lpd.dispose();
      throw re;
    } finally {
      lpd.processingFinished();
      lpd.dispose();
    }
  }

  /**
   * (overridden) This method performs the processing, the result display and
   * the renaming.
   *
   * @see java.lang.Runnable#run()
   */
  public void run() {
    this.pruneStruct = new DirectoryPruneStructure();
    // Tell the pruning, that the parents of the selection are not to be
    // deleted.
    File[] selection = renameConfig.getSelection();
    for (int i = 0; i < selection.length; i++) {
      if (selection[i].getParent() != null)
        pruneStruct.excludeDirectory(selection[i].getParent());
    }
    try {
      ListingProcessor lpc = new ListingProcessor(new FileProcessor(
          this.processingResult, renameConfig), renameConfig
          .getSelection());
      ListingProgressDialog lpd = new ListingProgressDialog(
          getParentFrame());
      lpd.setLocationRelativeTo(null);
      lpc.setListingProgressListener(lpd);
      lpd.setModal(false);
      lpd.setVisible(true);
      lpc.start().join();
      lpd.processingFinished();
      lpd.dispose();
      // If an exception has occured during processing
      if (lpc.getLastSeriouseException() != null) {
        // Throw it, this blocks exception catch diplays the error.
        throw lpc.getLastSeriouseException();
      }
      // If not aborted, display results
      if (!lpd.abort()) {
        /*
         * Now some statistics must be refreshed if the directory pattern
         * contains a bitrate.
         * If the bitrate pattern is replaced by its value it may
         * be possible a conflict arises.
         */
        refreshStatistics(getResult().getFileSystemRoots());
        InspectionControl control = new InspectionControl(this);
        control.displayInspector();
        if (!(aborted = control.isAborted())) {
          // Now the renaming can begin, using the targettreemodel
          renameFiles();
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
      JOptionPane.showMessageDialog(getParentFrame(), LangageManager
          .getProperty("tagrename.errormsg")
          + "\n" + e.getMessage(), LangageManager
          .getProperty("common.dialog.error"),
          JOptionPane.ERROR_MESSAGE);
    } finally {
      if (this.parentFrame != null
          && this.parentFrame instanceof TagEditorFrame) {
        ((TagEditorFrame) parentFrame).refreshCurrentTableView();
      }
    }
  }

  /**
   * This method enables or disables the copy of unmoveable files.
   *
   * @param b
   *            <code>true</code>, if unmoveable files should be copied
   */
  public void setCopyUnmodifiableFiles(boolean b) {
    if (getConfig().isCopyUnmodifiableFiles() != b) {
      getConfig().setCopyUnmodifiableFiles(b);
      // Update the bitrate patterns
      if (getConfig().getDirectoryPattern().containsBitrate()) {
        FileProcessor.processBitrate(getResult().getFileSystemRoots(),
            getConfig().isCopyUnmodifiableFiles());
        refreshStatistics(getResult().getFileSystemRoots());
      }
    }
  }
}
TOP

Related Classes of entagged.tageditor.tools.renaming.FileRenamer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.