Package com.mucommander.job

Source Code of com.mucommander.job.FileJob

/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 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.job;

import java.util.WeakHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.impl.CachedFile;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.job.progress.JobProgress;
import com.mucommander.job.ui.DialogResult;
import com.mucommander.job.ui.UserInputHelper;
import com.mucommander.text.Translator;
import com.mucommander.ui.dialog.QuestionDialog;
import com.mucommander.ui.dialog.file.ProgressDialog;
import com.mucommander.ui.main.FolderPanel;
import com.mucommander.ui.main.MainFrame;
import com.mucommander.ui.main.table.FileTable;
import com.mucommander.ui.notifier.AbstractNotifier;
import com.mucommander.ui.notifier.NotificationType;



/**
* FileJob is a container for a 'file task' : basically an operation that involves files and bytes.
* The class extending FileJob is required to give some information about the status of the job that
* will be used to display visual indications of the job's progress.
* <p>
* The actual processing is performed in a separate thread. A FileJob needs to be started explicitely using
* {@link #start()}. The lifecycle of a FileJob is as follows:<br>
* <br>
* <pre>
* {@link #NOT_STARTED} -> {@link #RUNNING} -> {@link #FINISHED}
*                         ^                |
*                         |                -> {@link #INTERRUPTED}
*                         |                |                     
*                         |                -> {@link #PAUSED} -|
*                         |                                    |
*                         -------------------------------------|
* </pre>
* </p>
*
* @author Maxence Bernard
*/
public abstract class FileJob implements Runnable {
  private static final Logger LOGGER = LoggerFactory.getLogger(FileJob.class);

    /** Thread in which the file job is performed */
    private Thread jobThread;

    /** Lock used when job is being paused */
    private final Object pauseLock = new Object();

    /** Timestamp in milliseconds when job started */
    private long startDate;

    /** Timestamp in milliseconds when job has finished */
    private long endDate;

    /** Number of milliseconds during which this job has been paused (been waiting for some user response).
     * Used to compute stats like average speed. */
    private long pausedTime;

    /** Contains the timestamp when this job has been put in pause (if in pause) */
    private long pauseStartDate;

    /** Associated dialog showing job progression */
    private ProgressDialog progressDialog;

    /** Main frame on which the job is to be performed */
    private MainFrame mainFrame;
 
    /** Base source folder */
    private AbstractFile baseSourceFolder;
 
    /** Files which are going to be processed */
    protected FileSet files;

    /** Number of files that this job contains */
    private int nbFiles;

    /** Index of file currently being processed, see {@link #getCurrentFileIndex()} */
    private int currentFileIndex = -1;

    /** File currently being processed */
    private AbstractFile currentFile;

    /** Name of the file currently being processed */
    private String currentFilename = "";

    /** If set to true, processed files will be unmarked from current table */
    private boolean autoUnmark = true;
 
    /** File to be selected after job has finished (can be null if not set) */
    private AbstractFile fileToSelect;

   
    /** Indicates that this job has not started yet, this is a temporary state */
    public final static int NOT_STARTED = 0;

    /** Indicates that this job is currently processing files, this is a temporary state */
    public final static int RUNNING = 1;

    /** Indicates that this job is currently paused, waiting for user response, this is a temporary state */
    public final static int PAUSED = 2;

    /** Indicates that this job has been interrupted by the end user, this is a permanent state */
    public final static int INTERRUPTED = 3;

    /** Indicates that this job has naturally finished (i.e. without being interrupted), this is a permanent state */
    public final static int FINISHED = 4;


    /** Current state of this job */
    private int jobState = NOT_STARTED;

    /** List of registered FileJobListener stored as weak references */
    private WeakHashMap<FileJobListener, ?> listeners = new WeakHashMap<FileJobListener, Object>();
   
    /** Information about this job progress */
    private JobProgress jobProgress;

    /** True if the user asked to automatically skip errors */
    private boolean autoSkipErrors;

//    private int nbFilesProcessed;
//    private int nbFilesDiscovered;

    protected final static int SKIP_ACTION = 0;
    protected final static int SKIP_ALL_ACTION = 1;
    protected final static int RETRY_ACTION = 2;
    protected final static int CANCEL_ACTION = 3;
    protected final static int APPEND_ACTION = 4;
    protected final static int OK_ACTION = 5;

    protected final static String SKIP_TEXT = Translator.get("skip");
    protected final static String SKIP_ALL_TEXT = Translator.get("skip_all");
    protected final static String RETRY_TEXT = Translator.get("retry");
    protected final static String CANCEL_TEXT = Translator.get("cancel");
    protected final static String APPEND_TEXT = Translator.get("resume");
    protected final static String OK_TEXT = Translator.get("ok");


    /**
     * Creates a new FileJob without starting it.
     *
     * @param progressDialog dialog which shows this job's progress
     * @param mainFrame mainFrame this job has been triggered by
     * @param files files which are going to be processed
     */
    public FileJob(ProgressDialog progressDialog, MainFrame mainFrame, FileSet files) {
        this(mainFrame, files);
        this.progressDialog = progressDialog;
    }

 
    /**
     * Creates a new FileJob without starting it, and with no associated ProgressDialog.
     *
     * @param mainFrame mainFrame this job has been triggered by
     * @param files files which are going to be processed
     */
    public FileJob(MainFrame mainFrame, FileSet files) {
        this.mainFrame = mainFrame;
        this.files = files;
        this.nbFiles = files.size();
        this.baseSourceFolder = files.getBaseFolder();

        // Create CachedFile instances around the source files in order to cache the return value of frequently accessed
        // methods. This eliminates some I/O, at the (small) cost of a bit more CPU and memory. Recursion is enabled
        // so that children and parents of the files are also cached.
        // Note: When cached methods are called, they no longer reflect changes in the underlying files. In particular,
        // changes of size or date could potentially not be reflected when files are being processed but this should
        // not really present a risk.
        AbstractFile tempFile;
        for(int i=0; i<nbFiles; i++) {
            tempFile = files.elementAt(i);
            files.setElementAt((tempFile instanceof CachedFile)?tempFile:new CachedFile(tempFile, true), i);
        }

        if (this.baseSourceFolder!=null)
            this.baseSourceFolder = (getBaseSourceFolder() instanceof CachedFile)?getBaseSourceFolder():new CachedFile(getBaseSourceFolder(), true);

      this.jobProgress = new JobProgress(this);   
    }
 
 
    /**
     * Specifies whether or not files that have been processed should be unmarked from current table (enabled by default).
     *
     * @param autoUnmark <code>true</code> to automatically unmark files after they have been processed.
     */
    public void setAutoUnmark(boolean autoUnmark) {
        this.autoUnmark = autoUnmark;
    }

    /**
     * Sets whether or not this file job should automatically skip errors when encountered (disabled by default).
     *
     * @param autoSkipErrors <code>true</code> to automatically skip errors, <code>false</code> to show an error dialog.
     */
    public void setAutoSkipErrors(boolean autoSkipErrors) {
        this.autoSkipErrors = autoSkipErrors;
    }

 
    /**
     * Sets the given file to be selected in the active table after this job has finished.
     * The file will only be selected if it exists in the active table's folder and if this job hasn't
     * been cancelled. The selection will occur after the tables have been refreshed (if they are refreshed).
     *
     * @param file the file to be selected in the active table after this job has finished
     */
    protected void selectFileWhenFinished(AbstractFile file) {
        this.fileToSelect = file;
    }
 
 
    /**
     * Starts file job in a separate thread.
     */
    public void start() {
        // Return if job has already been started
        if(getState()!=NOT_STARTED)
            return;

        // Pause auto-refresh during file job as it potentially modifies the current folders contents
        // and would potentially cause folder panel to auto-refresh
        getMainFrame().getLeftPanel().getFolderChangeMonitor().setPaused(true);
        getMainFrame().getRightPanel().getFolderChangeMonitor().setPaused(true);

        setState(RUNNING);
        startDate = System.currentTimeMillis();

        jobThread = new Thread(this, getClass().getName());
        jobThread.start();
    }


  /**
   * Returns the dialog showing progress of this job.
   * @return the progressDialog
   */
  protected ProgressDialog getProgressDialog() {
    return progressDialog;
  }


  /**
   * Returns the main frame.
   * @return the mainFrame
   */
  protected MainFrame getMainFrame() {
    return mainFrame;
  }


  /**
     * Returns the current state of this FileJob. See constant fields for possible return values.
     *
     * @return the current state of this FileJob. See constant fields for possible return values.
     */
    public int getState() {
        return jobState;
    }

    /**
     * Sets a new state for this FileJob and notifies registered FileJobListener instances of the change.
     *
     * @param jobState the new state
     */
    protected void setState(int jobState) {
        int oldState = this.jobState;
        this.jobState = jobState;

        for(FileJobListener listener : listeners.keySet())
            listener.jobStateChanged(this, oldState, jobState);
    }


    /**
     * Registers a FileJobListener to receive notifications whenever state of this FileJob changes.
     *
     * <p>Listeners are stored as weak references so {@link #removeFileJobListener(FileJobListener)}
     * doesn't need to be called for listeners to be garbage collected when they're not used anymore.</p>
     *
     * @param listener the FileJobListener to register
     */
    public void addFileJobListener(FileJobListener listener) {
        listeners.put(listener, null);
    }

    /**
     * Removes the given FileJobListener from the list of listeners that receive notifications when the state of
     * this FileJob has changed.
     *
     * @param listener the FileJobListener to remove
     */
    public void removeFileJobListener(FileJobListener listener) {
        listeners.remove(listener);
    }


    /**
     * Returns the timestamp in milliseconds when this job started.
     *
     * @return the timestamp in milliseconds when this job started
     */
    public long getStartDate() {
        return startDate;
    }

    /**
     * Returns the timestamp in milliseconds when this job ended, <code>0</code> if this job hasn't finished yet.
     *
     * @return the timestamp in milliseconds when this job ended
     */
    public long getEndDate() {
        return endDate;
    }

    /**
     * Returns the timestamp in milliseconds when this job was last paused, <code>0</code> if this job has not been
     * paused yet.
     *
     * @return the timestamp in milliseconds when this job was last paused
     */
    public long getPauseStartDate() {
        return pauseStartDate;
    }
   
    /**
     * Sets the timestamp in miliseconds when this job is paused.
     */
    private void setPauseStartDate() {
        this.pauseStartDate = System.currentTimeMillis();
    }

   
    /**
     * Returns the number of milliseconds during which this job has been paused (been waiting for some user response).
     * If this job has been paused several times, the total is returned. If this job has not been paused yet,
     * <code>0</code> is returned.
     *
     * @return the number of milliseconds during which this job has been paused
     */
    public long getPausedTime() {
        return pausedTime;
    }

    /**
     * Adds a time of last pause to this job pause time counter.
     */
    private void calcPausedTime() {
        this.pausedTime += System.currentTimeMillis() - this.getPauseStartDate();
    }


    /**
     * Returns the number of milliseconds this job effectively spent processing files, exclusing any pause time.
     *
     * @return the number of milliseconds this job effectively spent processing files, exclusing any pause time
     */
    public long getEffectiveJobTime() {
        // If job hasn't start yet, return 0
        if(getStartDate()==0)
            return 0;
       
        return (getEndDate()==0?System.currentTimeMillis():getEndDate())-getStartDate()-getPausedTime();
    }

   
    /**
     * Interrupts this job, changes the job state to {@link #INTERRUPTED} and notifies listeners.
     */ 
    public void interrupt() {
        int state = getState();
        if(state==INTERRUPTED || state==FINISHED)
            return;

        if(state==PAUSED)
            setPaused(false);

        // Set state before calling stop() so that state is INTERRUPTED when jobStopped() is called
        // (some FileJob rely on that)
        setState(INTERRUPTED);

        stop();
    }


    /**
     * Release reference to thread and store job's end date.
     */
    private void stop() {
        // Return if job has already been stopped
        if(jobThread==null)
            return;

//        // Start by calling interrupt to have the thread return from any blocking I/O occurring in an interruptible
//        // channel or selector.
//        jobThread.interrupt();

        jobThread = null;
        endDate = System.currentTimeMillis();

        // Notify that the job has been stopped
        jobStopped();
    }

 
    /**
     * Sets or unsets this job in paused mode.
     */
    public void setPaused(boolean paused) {
        // Lock the pause lock while updating paused status
        synchronized(pauseLock) {
            // Resume job if it was paused
            if(!paused && getState()==PAUSED) {
                // Calculate pause time
                calcPausedTime();               
                // Call the jobResumed method to notify of the new job's state
                jobResumed();

                // Wake up the job's thread that is potentially waiting for pause to be over
                pauseLock.notify();

                // Switch to RUNNING state and notify listeners
                setState(RUNNING);
            }
            // Pause job if it not paused already
            else if(paused && getState()!=PAUSED && getState()!=INTERRUPTED && getState()!=FINISHED) {
                // Memorize pause time in order to calculate pause time when the job is resumed
                setPauseStartDate();
                // Call the jobPaused method to notify of the new job's state
                jobPaused();

                // Switch to PAUSED state and notify listeners
                setState(PAUSED);
            }
        }
    }


    /**
     * Changes the current file. This method should be called by subclasses whenever the job
     * starts processing a new file other than a top-level file, i.e. one that was passed
     * as an argument to {@link #processFile(AbstractFile, Object) processFile()}.
     * ({#nextFile(AbstractFile) nextFile()} is automatically called for files in base folder).
     */
    protected void nextFile(AbstractFile file) {
        this.setCurrentFile(file);

//        // Notify ProgressDialog (if any) that a new file is being processed
//        if(progressDialog!=null)
//            progressDialog.notifyCurrentFileChanged();
       
        // Lock the pause lock
        synchronized(pauseLock) {
            // Loop while job is paused, there shouldn't normally be more than one loop
            while(getState()==PAUSED) {
                try {
                    // Wait for a call to notify()
                    pauseLock.wait();
                } catch(InterruptedException e) {
                    // No more problem, loop one more time
                }
            }
        }
//        if(this.currentFile!=null)
//            this.nbFilesProcessed++;
    }


//    protected void fileDiscovered(AbstractFile file) {
//        this.nbFilesDiscovered++;
//    }
//
//    protected void filesDiscovered(AbstractFile files[]) {
//        this.nbFilesDiscovered += files.length;
//    }
//
//    protected int getNbFilesDiscovered() {
//        return this.nbFilesDiscovered;
//    }
//
//    protected int getNbFilesProcessed() {
//        return this.nbFilesProcessed;
//    }
//

    /**
     * Returns the name of the file currently being processed surrounded by simple quotes (e.g. 'test.zip'), or an empty
     * string if no file is currently being processed.
     *
     * @return the name of the file currently being processed surrounded by simple quotes, or an empty string if no file
     * is currently being processed
     */
    protected String getCurrentFilename() {
        return currentFilename;
    }


    /**
     * This method is called when this job starts, before the first call to {@link #processFile(AbstractFile,Object)} is made.
     * This method implementation does nothing but it can be overriden by subclasses to perform some first-time initializations.
     */
    protected void jobStarted() {
        LOGGER.debug("called");
    }
 

    /**
     * This method is called when this job has completed normal execution : all files have been processed without any interruption
     * (without any call to {@link #interrupt()}).
     *
     * <p>The call happens after the last call to {@link #processFile(AbstractFile,Object)} is made.
     * This method implementation does nothing but it can be overriden by subclasses to properly complete the job.</p>
  
     * <p>Note that this method will NOT be called if a call to {@link #interrupt()} was made before all files were processed.</p>
     */
    protected void jobCompleted() {
        LOGGER.debug("called");

        // Send a system notification if a notifier is available and enabled
        if(AbstractNotifier.isAvailable() && AbstractNotifier.getNotifier().isEnabled())
            AbstractNotifier.getNotifier().displayBackgroundNotification(NotificationType.JOB_COMPLETED,
                    getProgressDialog()==null?"":getProgressDialog().getTitle(),
                    Translator.get("progress_dialog.job_finished"));
    }


    /**
     * This method is called when this job has been paused, either by the user, or by the job when asking for user input.
     *
     * <p>This method implementation does nothing but it can be overriden by subclasses to do whatever is needed
     * when the job has been paused.
     */
    protected void jobPaused() {
        LOGGER.debug("called");
    }


    /**
     * This method is called when this job has been resumed after being paused.
     *
     * <p>This method implementation does nothing but it can be overriden by subclasses to do whatever is needed
     * when the job has returned from pause.
     */
    protected void jobResumed() {
        LOGGER.debug("called");
    }


    /**
     * This method is called when this job has been stopped. The call happens after all calls to {@link #processFile(AbstractFile,Object)} and
     * {@link #jobCompleted()}.
     * This method implementation does nothing but it can be overriden by subclasses to properly terminate the job.
     * This is where you want to close any opened connections.
     *
     * <p>Note that unlike {@link #jobCompleted()} this method is always called, whether the job has been completed (all
     * files were processed) or has been interrupted in the middle.</p>
     */
    protected void jobStopped() {
        LOGGER.debug("called");
    }
 
 
    /**
     * Displays an error dialog with the specified title and message,
     * offers to skip the file, retry or cancel and waits for user choice.
     * The job is stopped if 'cancel' or 'close' was chosen, and the result
     * is returned.
     */
    protected int showErrorDialog(String title, String message) {
        String actionTexts[] = new String[]{SKIP_TEXT, SKIP_ALL_TEXT, RETRY_TEXT, CANCEL_TEXT};
        int actionValues[] = new int[]{SKIP_ACTION, SKIP_ALL_ACTION, RETRY_ACTION, CANCEL_ACTION};

        return showErrorDialog(title, message, actionTexts, actionValues);
    }


 
    /**
     * Displays an error dialog with the specified title and message and returns the selection action's value.
     */
    protected int showErrorDialog(String title, String message, String actionTexts[], int actionValues[]) {
        // Return SKIP_ACTION if 'skip all' has previously been selected and 'skip' is in the list of actions.
        if(autoSkipErrors) {
            for (int actionValue : actionValues)
                if (actionValue == SKIP_ACTION)
                    return SKIP_ACTION;
        }

        // Send a system notification if a notifier is available and enabled
        if(AbstractNotifier.isAvailable() && AbstractNotifier.getNotifier().isEnabled())
            AbstractNotifier.getNotifier().displayBackgroundNotification(NotificationType.JOB_ERROR, title, message);

        QuestionDialog dialog;
        if(getProgressDialog()==null)
            dialog = new QuestionDialog(getMainFrame(),
                                        title,
                                        message,
                                        getMainFrame(),
                                        actionTexts,
                                        actionValues,
                                        0);
        else
            dialog = new QuestionDialog(getProgressDialog(),
                                        title,
                                        message,
                                        getMainFrame(),
                                        actionTexts,
                                        actionValues,
                                        0);

        // Cancel or close dialog stops this job
        int userChoice = waitForUserResponse(dialog);
        if(userChoice==-1 || userChoice==CANCEL_ACTION)
            interrupt();
        // Keep 'skip all' choice for further error and return SKIP_ACTION
        else if(userChoice==SKIP_ALL_ACTION) {
            autoSkipErrors = true;
            return SKIP_ACTION;
        }

        return userChoice;
    }
 
 
    /**
     * Waits for the user's answer to the given question dialog, putting this
     * job in pause mode while waiting for the user.
     */
    protected int waitForUserResponse(DialogResult dialog) {
        Object userInput = waitForUserResponseObject(dialog);
        return (Integer) userInput;
    }
   
    protected Object waitForUserResponseObject(DialogResult dialog) {
        // Put this job in pause mode while waiting for user response
        setPaused(true);
       
        UserInputHelper jobUserInput = new UserInputHelper(this, dialog);
        Object userInput = jobUserInput.getUserInput();
       
        // Back to work
        setPaused(false);
        return userInput;
    }
   
 
 
    /**
     * Check and if needed, refreshes both file tables's current folders, based on the job's refresh policy.
     */
    protected void refreshTables() {
      FolderPanel activePanel = getMainFrame().getActivePanel();
      FolderPanel inactivePanel = getMainFrame().getInactivePanel();

        if(hasFolderChanged(inactivePanel.getCurrentFolder()))
          inactivePanel.tryRefreshCurrentFolder();

        if(hasFolderChanged(activePanel.getCurrentFolder())) {
            // Select file specified by selectFileWhenFinished (if any) only if the file exists in the active table's folder
            if(fileToSelect!=null && activePanel.getCurrentFolder().equalsCanonical(fileToSelect.getParent()) && fileToSelect.exists())
              activePanel.tryRefreshCurrentFolder(fileToSelect);
            else
              activePanel.tryRefreshCurrentFolder();
        }

        // Repaint the status bar as marked files have changed
        mainFrame.getStatusBar().updateSelectedFilesInfo();

        // Resume current folders auto-refresh
        getMainFrame().getLeftPanel().getFolderChangeMonitor().setPaused(false);
        getMainFrame().getRightPanel().getFolderChangeMonitor().setPaused(false);
    }
 

    /**
     * Returns this job's percentage of completion, as a float comprised between 0 and 1.
     *
     * @return this job's percentage of completion, as a float comprised between 0 and 1
     */
    public float getTotalPercentDone() {
        return getCurrentFileIndex()/(float)getNbFiles();
    }


    /**
     * Returns the index of the file currently being processed, {@link #getNbFiles()} if all files have been processed.
     *
     * @return the index of the file currently being processed, {@link #getNbFiles()} if all files have been processed
     */
    public int getCurrentFileIndex() {
        return currentFileIndex==-1?0:currentFileIndex;
    }
   
    /**
     * Returns the file currently being processed.
     * @return the file currently being processed.
     */
    public AbstractFile getCurrentFile() {
      return currentFile;
    }
   
    /**
     * Sets the file currently being processed.
     * @param file the file currently being processed.
     */
    private void setCurrentFile(AbstractFile file) {
        this.currentFile = file;
        // Update current file information returned by getCurrentFilename()
        this.currentFilename = "'" + file.getName() + "'";
    }

    /**
     * Returns the number of file that this job contains.
     *
     * @return the number of file that this job contains
     */
    public int getNbFiles() {
        return nbFiles;
    }
   
    /**
     * Sets the number of files that this job contains.
     *
     * @param nbFiles the number of files that this job contains.
     */
    protected void setNbFiles(int nbFiles) {
      this.nbFiles = nbFiles;
    }

    /**
     * Returns a String describing what the job is currently doing. This default implementation returns
     * <i>Processing CURRENT_FILE</i> where CURRENT_FILE is the name of the file currently being processed.
     * This method should be overridden to provide a more accurate description.
     *
     * @return a String describing what the job is currently doing
     */
    public String getStatusString() {
        return Translator.get("progress_dialog.processing_file", getCurrentFilename());
    }

    /**
     * Returns information about the job progress.
     * @return the job progress
     */
  public JobProgress getJobProgress() {
    return jobProgress;   
  }

    /**
     * Returns the base source folder.
     * @return the baseSourceFolder
     */
    protected AbstractFile getBaseSourceFolder() {
        return baseSourceFolder;
    }
 
 
    /////////////////////////////
    // Runnable implementation //
    /////////////////////////////

    /**
     * This method is public as a side-effect of this class implementing <code>Runnable</code>.
     */
    public final void run() {
        FileTable activeTable = getMainFrame().getActiveTable();
        AbstractFile currentFile;

        // Notify that this job has started
        jobStarted();

//this.nbFilesDiscovered += nbFiles;

        // Loop on all source files, checking that job has not been interrupted
        for(int i=0; i<nbFiles; i++) {
            currentFile = files.elementAt(i);

            // Change current file and advance file index
            currentFileIndex = i;
            nextFile(currentFile);

            // Process current file
            boolean success = processFile(currentFile, null);

            // Stop if job was interrupted
            if(getState()==INTERRUPTED)
                break;

            // Unmark file in active table if 'auto unmark' is enabled
            // and file was processed successfully
            if(autoUnmark && success) {
                // Do not repaint rows individually as it would be too expensive
                activeTable.setFileMarked(currentFile, false, false);
            }

            // If last file was reached without any user interruption, all files have been processed with or
            // without errors, switch to FINISHED state and notify listeners
            if(i==nbFiles-1) {
                currentFileIndex++;
                stop();
                jobCompleted();
                setState(FINISHED);
            }
        }

        // Refresh tables's current folders, based on the job's refresh policy.
        refreshTables();
    }


    //////////////////////
    // Abstract methods //
    //////////////////////

    /**
     * Returns <code>true</code> if the given folder has or may have been modified by this job.
     * This method is called after this job has finished processing files, to determine if the current MainFrame's
     * file tables need to be refreshed to reveal the modified contents.
     *
     * @param folder the folder to test
     * @return true if the given folder has or may have been modified by this job
     */
    protected abstract boolean hasFolderChanged(AbstractFile folder);
 
 
    /**
     * Automatically called by {@link #run()} for each file that needs to be processed.
     *
     * @param file the file or folder to process
     * @param recurseParams array of parameters which can be used when calling this method recursively, contains <code>null</code> when called by {@link #run()}
     *
     * @return <code>true</code> if the operation was sucessful
     */
    protected abstract boolean processFile(AbstractFile file, Object recurseParams);


}
TOP

Related Classes of com.mucommander.job.FileJob

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.