/**
   * Deletes the contents of the temporary files folder, including subfolders
   *
   * @param monitor progress monitor
   * @throws CoreException if an error occurs
   */
  public void cleanTempFiles(IProgressMonitor monitor) throws CoreException {
    if (tempDir != null && tempDir.exists()) {
      monitor.beginTask(
          TexlipsePlugin.getResourceString("builderSubTaskClean"), tempDir.members().length);
      monitor.subTask(TexlipsePlugin.getResourceString("builderSubTaskCleanTemp"));

      // Retrieve current temp folder content
      final Set<IPath> currentTmpFiles = tracking.getTempFolderNames(monitor);

      // Perform deletion
      deleteFiles(currentTmpFiles, monitor);
    }
    tracking.clearSnapshots();
  }
  /**
   * Performs actions before a LaTeX document is built; namely:
   *
   * <ul>
   *   <li>memorizing which files are present in the temporary and build source folder, and
   *   <li>moving temporary files from their folder into the build folder, so the build process has
   *       access to them.
   * </ul>
   *
   * @param monitor progress monitor
   * @throws CoreException if an error occurs
   */
  public void performBeforeBuild(IProgressMonitor monitor) throws CoreException {
    // capture current state of build and temp folder
    tracking.refreshSnapshots(sourceDir, monitor);

    // use temp files from previous build
    restoreTempFiles(monitor);
  }
  /**
   * Performs actions after the LaTeX builder has finished building a document for the current
   * source; namely:
   *
   * <ul>
   *   <li>renaming and/or moving output (and other derived) files out of the build directory into
   *       the output folder
   *   <li>moving old and new temporary files out of the build directory into the temporary files
   *       folder
   * </ul>
   *
   * @param inputFile name of the input file; this can be <code>null</code>, if the current main
   *     document has just been built, but should be set after partial builds
   * @param monitor progress monitor
   * @throws CoreException if an error occurs
   */
  public void performAfterBuild(IProgressMonitor monitor) throws CoreException {
    // keeping first exception, which occurs when moving files; however, attempts
    // to still perform following steps
    CoreException ex = null;

    // make sure this has access to all files (if this fails, it means trouble to
    // all following steps)
    refreshView(monitor);

    Set<IPath> outputFiles = null;
    try { // possibly move output files away from the source dir and mark as derived
      outputFiles = moveOutputFiles(monitor);
    } catch (CoreException e) {
      // store exception for throwing it later
      ex =
          new BuilderCoreException(
              TexlipsePlugin.stat(TexlipsePlugin.getResourceString("builderCoreErrorOutputBlock")));
    }

    try { // move temp files out of this folder and mark as derived
      moveTempFiles(outputFiles, monitor);
    } catch (CoreException e) {
      // we only worry about this one, if the build was okay
      if (ex == null) {
        ex =
            new BuilderCoreException(
                TexlipsePlugin.stat(TexlipsePlugin.getResourceString("builderCoreErrorTempBlock")));
      }
    }

    try {
      refreshView(monitor);
    } catch (CoreException e) {
      // this is not irrelevant, but not as severe as the others
      if (ex == null) {
        ex = e;
      }
    }

    tracking.clearSnapshots();
    // now throw any pending exception, after cleaning up
    if (ex != null) {
      throw ex;
    }
  }
  /**
   * Moves all files currently located in the temporary files folder into the build directory
   *
   * @param monitor progress monitor
   * @throws CoreException if an error occurs
   */
  private void restoreTempFiles(IProgressMonitor monitor) throws CoreException {
    final Set<IPath> tempNames = tracking.getTempFiles();
    if (tempDir == null || tempNames.isEmpty()) {
      movedFiles = new HashSet<IPath>();
      return;
    }

    // Move files and store new paths for later reversal
    project
        .getWorkspace()
        .run(
            new IWorkspaceRunnable() {
              public void run(IProgressMonitor monitor) throws CoreException {
                movedFiles =
                    moveFiles(tempDir, sourceDir, tracking.getTempFiles(), false, false, monitor);
              }
            },
            monitor);
  }
  /**
   * Moves temporary files out of the build directory, if applicable. A file is considered a
   * temporary file, if
   *
   * <ul>
   *   <li>it had been in the temporary files folder before the build process
   *       <p><b>or</b>
   *   <li>it was created or modified during the build process, and has a temporary file extension
   *       as specified in the preferences
   * </ul>
   *
   * @param excludes set of paths to exclude from moving, e.g. because they are the main output
   *     files
   * @param monitor progress monitor
   * @throws CoreException if an error occurs
   */
  private void moveTempFiles(final Set<IPath> excludes, IProgressMonitor monitor)
      throws CoreException {
    final IContainer aSourceContainer = getActualSourceContainer();
    if (tracking.isInitial() || aSourceContainer == null || !aSourceContainer.exists()) {
      return;
    }

    final boolean markAsDerived =
        "true"
            .equals(
                TexlipseProperties.getProjectProperty(
                    project, TexlipseProperties.MARK_TEMP_DERIVED_PROPERTY));
    final String[] tempExts = TexlipsePlugin.getPreferenceArray(TexlipseProperties.TEMP_FILE_EXTS);

    // Check if there is anything to do
    if (markAsDerived || tempDir != null) {
      // First move temporary files, which had been placed into the source folder
      // just prior to the build;
      // then check for new temporary files, which need to be moved
      project
          .getWorkspace()
          .run(
              new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                  if (movedFiles != null) {
                    if (excludes != null) {
                      movedFiles.removeAll(excludes);
                    }
                    moveFiles(sourceDir, tempDir, movedFiles, markAsDerived, true, monitor);
                  }
                  final Set<IPath> newTempNames =
                      tracking.getNewTempNames(aSourceContainer, tempExts, format, monitor);
                  if (excludes != null) {
                    newTempNames.removeAll(excludes);
                  }
                  moveFiles(sourceDir, tempDir, newTempNames, markAsDerived, true, monitor);
                }
              },
              monitor);
    }
  }
  /**
   * Renames output files and/or moves them if necessary. A file is considered an output file, if
   *
   * <ul>
   *   <li>it is the current output file (which can also be from a temporary build)
   *       <p><b>or</b>
   *   <li>it has the same file name as the current input file, apart from its file extension, and
   *       one of the derived file extensions as specified in the preferences
   * </ul>
   *
   * @param monitor progress monitor
   * @return set of paths to the (possibly moved) files
   * @throws CoreException if an error occurs
   */
  private Set<IPath> moveOutputFiles(IProgressMonitor monitor) throws CoreException {
    final boolean markAsDerived =
        "true"
            .equals(
                TexlipseProperties.getProjectProperty(
                    project, TexlipseProperties.MARK_OUTPUT_DERIVED_PROPERTY));
    final String[] derivedExts =
        TexlipsePlugin.getPreferenceArray(TexlipseProperties.DERIVED_FILES);

    final IFile aSourceFile = getActualSourceFile();
    final IContainer aSourceContainer = getActualSourceContainer();
    final IFile sOutputFile = getSelectedOutputFile();
    final IContainer sOutputContainer = getSelectedOutputContainer(markAsDerived, monitor);
    if (aSourceFile == null
        || aSourceContainer == null
        || sOutputFile == null
        || sOutputContainer == null) {
      // Something is wrong with the settings
      return null;
    }

    // Get name without extension from main files for renaming
    final String dotFormat = '.' + format;
    final String sourceBaseName = stripFileExt(aSourceFile.getName(), null);
    final String outputBaseName = stripFileExt(sOutputFile.getName(), dotFormat);

    // Check if files are to be moved or renamed
    final boolean moveFiles =
        !sourceBaseName.equals(outputBaseName) || !sOutputContainer.equals(aSourceContainer);
    // Retrieve output and other derived files along with their extensions
    final Map<IPath, String> outputFiles =
        ProjectFileTracking.getOutputNames(
            aSourceContainer, sourceBaseName, derivedExts, format, monitor);

    // Check if there is anything to do
    if ((moveFiles || markAsDerived) && !outputFiles.isEmpty()) {
      final Set<IPath> movedFiles = new HashSet<IPath>(outputFiles.size());

      project
          .getWorkspace()
          .run(
              new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                  // Move files to destination folder and rename
                  for (Entry<IPath, String> entry : outputFiles.entrySet()) {
                    IFile currentFile = project.getFile(entry.getKey());
                    if (moveFiles) {
                      // Determine new file name
                      String destName = outputBaseName + entry.getValue();
                      // Move file
                      IFile dest =
                          moveFile(project, currentFile, sOutputContainer, destName, monitor);
                      if (dest != null && markAsDerived) {
                        dest.setDerived(true);
                      }
                      movedFiles.add(dest.getProjectRelativePath());
                    } else {
                      // Possibly mark as derived
                      if (markAsDerived) {
                        currentFile.setDerived(true);
                      }
                      movedFiles.add(entry.getKey());
                    }
                  }
                }
              },
              monitor);

      return movedFiles;
    } else {
      return outputFiles.keySet();
    }
  }