/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 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.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.EventListenerList; import com.mucommander.commons.file.AbstractFile; /** * A class that monitors jobs progress. * @author Arik Hadas, Mariusz Jakubowski * */ public class JobsManager implements FileJobListener { /** Controls how often should current file label be refreshed (in ms) */ private final static int CURRENT_FILE_LABEL_REFRESH_RATE = 100; /** Controls how often should progress information be refreshed */ private final static int MAIN_REFRESH_RATE = 10; /** Time after which remove finished job from a monitor */ private final static int FINISHED_JOB_REMOVE_TIME = 1500; /** Timer used to monitor jobs progress */ private Timer progressTimer; /** List of listeners */ private EventListenerList listenerList = new EventListenerList(); /** A list of monitored jobs. */ private List<FileJob> jobs; /** An instance of this class */ private static final JobsManager instance = new JobsManager(); /** * Creates a new JobsManager instance. */ private JobsManager() { JobProgressTimer timerListener = new JobProgressTimer(); progressTimer = new Timer(CURRENT_FILE_LABEL_REFRESH_RATE, timerListener); jobs = new CopyOnWriteArrayList<>(); } /** * Returns the instance of JobsManager. * @return the instance of JobsManager. */ public static JobsManager getInstance() { return instance; } /** * Adds a listener to the list that's notified each time a job * progress is updated. * * @param l the JobListener */ public void addJobListener(JobListener l) { listenerList.add(JobListener.class, l); } /** * Removes a listener from the list that's notified each time job * progress is updated. * * @param l the JobListener */ public void removeJobListener(JobListener l) { listenerList.remove(JobListener.class, l); } /** * Forwards the progress notification event to all * <code>JobListeners</code> that registered * themselves as listeners. * @param source a job for which the progress has been updated * @param fullUpdate if false only file label has been updated * * @see #addJobListener * @see JobListener#jobProgress */ private void fireJobProgress(FileJob source, boolean fullUpdate) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { ((JobListener)listeners[i+1]).jobProgress(source, fullUpdate); } } private void fireJobAdded(FileJob source) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { ((JobListener)listeners[i+1]).jobAdded(source); } } private void fireJobRemoved(FileJob source) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { ((JobListener)listeners[i+1]).jobRemoved(source); } } /** * Adds a new job to the list of monitored jobs. * This method is executed in Swing Thread (EDT). * After adding a new job a {@link JobListener#jobAdded(FileJob)} * event is fired. * @param job a job to be added */ public void addJob(final FileJob job) { // ensure that this method is called in EDT if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(() -> addJob(job)); return; } jobs.add(job); if (job.isRunInBackground()) { fireJobAdded(job); } if (!progressTimer.isRunning()) { progressTimer.start(); } job.addFileJobListener(this); } /** * Removes a job from a list of monitored jobs. * This method is executed in Swing Thread (EDT). * After removing a job a {@link JobListener#jobRemoved(FileJob)} * event is fired. * @param job a job to be removed */ private void removeJob(final FileJob job) { // ensure that this method is called in EDT if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(() -> removeJob(job)); return; } jobs.remove(job); fireJobRemoved(job); if (jobs.isEmpty()) { progressTimer.stop(); } job.removeFileJobListener(this); } /** * Returns number of monitored jobs. * @return number of monitored jobs. */ public int getJobCount() { return jobs.size(); } /** * Checks if the given folder may change by any of the existing running job. * @param folder a folder to check * @return true if the folder may change by existing job, false otherwise */ public boolean mayFolderChangeByExistingJob(AbstractFile folder) { return jobs.stream() .filter(job -> job.getState() != FileJobState.PAUSED) .anyMatch(job -> job.hasFolderChanged(folder)); } /** * Removes the given job from the jobs collection. Should be called when the job is ended. * @param job a job to remove */ void jobEnded(FileJob job) { Timer timer = new Timer(FINISHED_JOB_REMOVE_TIME, event -> removeJob(job)); timer.setRepeats(false); timer.start(); } /** * Returns jobs that are running in the background. * @return jobs that are running in the background. */ public List<FileJob> getBackgroundJobs() { return jobs.stream() .filter(FileJob::isRunInBackground) .collect(Collectors.toList()); } /** * Returns a progress of a job with specified index. * @param rowIndex an index of a job * @return a progress information or null if job doesn't exists */ public JobProgress getJobProgres(int rowIndex) { if (rowIndex < jobs.size()) { FileJob job = jobs.get(rowIndex); return job.getJobProgress(); } return null; } /** * A {@link FileJobListener} implementation. * Removes a finished job after a small delay. */ @Override public void jobStateChanged(final FileJob source, FileJobState oldState, FileJobState newState) { } @Override public void jobExecutionModeChanged(FileJob source, boolean background) { if (background) fireJobAdded(source); else fireJobRemoved(source); } /** * * This class implements a listener for a job progress timer. * */ private class JobProgressTimer implements ActionListener { /** a loop index indicating if this refresh is partial (label only) or full */ private int loopCount; public void actionPerformed(ActionEvent e) { loopCount++; boolean fullUpdate; if (loopCount >= MAIN_REFRESH_RATE) { fullUpdate = true; loopCount = 0; } else { fullUpdate = false; } // for each job calculate new progress and notify listeners for (FileJob job : jobs) { JobProgress jobProgress = job.getJobProgress(); boolean updateFullUI = jobProgress.calcJobProgress(fullUpdate); fireJobProgress(job, updateFullUI); } } } }