/**
* BackgroundMonitor.java
*
* Copyright (c) 2009 Brian Gibowski <brian@brgib.com>. All rights reserved.
*
* This file is part of iTunesDSM.
*
* iTunesDSM 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.
*
* iTunesDSM 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 iTunesDSM. If not, see <http://www.gnu.org/licenses/>.
*/
package core.background;
import data.OutputStringArrayList;
import display.Display;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker.StateValue;
import task.AddTracksTask;
import ui.SysTrayMain;
import ui.Status;
/**
*
* The BackgroundMonitor class is used to start and stop a FolderWatcher instance.
* The class also sets listeners on the AllFiles object, and FolderWatcher instances
* through a Status interface to monitor progress and report it to the user.
*
*
* @author Brian Gibowski brian@brgib.com
*/
public class BackgroundMonitor extends Thread{
/**
* The default time to check the directory is 1 hour.
* This value is 1 hour represented in milliseconds.
*/
public static final long DEFAULT_SLEEP_TIME_MILLISECS = 3600000L;
/**
* The default time to check the directory is 1 hours.
*/
public static final int DEFAULT_SLEEP_TIME_HOURS =
(int)DEFAULT_SLEEP_TIME_MILLISECS / 60 / 60 / 1000;
private Logger log = Logger.getLogger("global");
private Status status;
private long timeDelay;
private Date nextRunTime;
private boolean runATT = false;
private AddTracksTask a;
private ArrayList<BackgroundMonitorStatusListener> listeners =
new ArrayList<BackgroundMonitorStatusListener>();
/**
* Creates a new BackgroundMonitor object with the default time delay.
* The background monitor thread is automatically started.
*
*/
public BackgroundMonitor(){
this(DEFAULT_SLEEP_TIME_MILLISECS);
}
/**
* Creates a new BackgroundMonitor object. The status object must be initialized
* here with the setStatus method before starting or stopping a background
* monitor object. Otherwise NullPointerExceptions will be thrown.
*
* @param timeHoursDelay The time in hours to delay execution of searching for new tracks.
*/
public BackgroundMonitor(int timeHoursDelay){
this((long)(60 * 60 * 1000 * timeHoursDelay));
}
/**
* Creates a new BackgroundMonitor object with the given time delay in milliseconds.
* The background monitor thread is automatically started within this constructor.
*
* @param timeDelay The amount of time between new track searches in milliseconds.
*/
public BackgroundMonitor(long timeDelay){
this.timeDelay = timeDelay;
this.start();
this.setPriority(Thread.MIN_PRIORITY);
}
/**
* Sets the time delay to the given long value.
*
* @param delay The amount of time between searches for new tracks in
* milliseconds.
*/
public void setTimeDelay(long delay) {
this.timeDelay = delay;
}
/**
* Sets the time delay to the given time in hours.
*
* @param hours The amount of time between searches for new tracks in
* hours.
*/
public void setTimeDelayHours(int hours){
this.timeDelay = (long)(hours * 60 * 60 * 1000);
}
/**
* Sets the status object the background monitor object should
* report status information to.
*
* @param status The status object that the Background Monitor object
* will report status updates too. If the status object has not
* been initialized NullPointerExceptions will be thrown.
*/
public void setStatus(Status status){
this.status = status;
}
/**
* Stops the folder watcher instance if it is running. The status
* instance and the system tray icon report the stopped folder watcher
* as well.
*
*/
public void stopBackgroundMonitor(){
if(a != null && !a.isDone()){
a.cancel(true);
}
runATT = false;
setNextRun(null);
dispatchBackgroundMonitorStatusEvents(BackgroundMonitorStatusEvent.STOPPED,
BackgroundMonitorStatusEvent.NON_PROGRESS_EVENT);
}
/**
* Starts a new Folder Watcher instance given the constructor variables. The
* requisite listeners are also applied to the folder watcher instance.
*
* @param filesToMonitor The directories to monitor.
*
* @param timeHoursDelay The amount of time between checks for changed folders
* in hours.
*/
public void startBackgroundMonitor(final ArrayList<File> filesToMonitor, final int timeHoursDelay){
setTimeDelayHours(timeHoursDelay);
if(filesToMonitor == null){
NullPointerException e =
new NullPointerException("Fie to monitor is null");
log.log(Level.SEVERE, this.getClass().getName(), e);
throw e;
}
if(isRunning()){
return;
}
final CheckDirsExist checkDirs = new CheckDirsExist(filesToMonitor);
checkDirs.addPropertyChangeListener(new PropertyChangeListener(){
@Override
public void propertyChange(PropertyChangeEvent e){
if(checkDirs.getState() == StateValue.DONE && !runATT){
try{
ArrayList<File> notDirs = checkDirs.get();
if(notDirs.size() > 0){ //if one or more dirs does not exist
Display.newWarningMessage("Directory to monitor",
"Sorry, iTunesDSM is unable to locate" +
" directories:\n" + getNotDirsPaths(notDirs) +
".\nBackground monitoring will cancel.");
runATT = false;
dispatchBackgroundMonitorStatusEvents(
BackgroundMonitorStatusEvent.STOPPED,
BackgroundMonitorStatusEvent.NON_PROGRESS_EVENT);
}
else{
setTimeDelay(SysTrayMain.OPTIONS.getBackgroundDelay());
setNextRun();
dispatchBackgroundMonitorStatusEvents(
BackgroundMonitorStatusEvent.STARTED,
BackgroundMonitorStatusEvent.NON_PROGRESS_EVENT);
runATT = true;
}
}
catch(InterruptedException ie){
log.log(Level.SEVERE, this.getClass().getName(), ie);
}
catch(ExecutionException ee){
log.log(Level.SEVERE, this.getClass().getName(), ee);
}
}
}
});
checkDirs.execute();
}
/**
* Returns whether or not a new add tracks task will execute after the
* background monitor thread has finished sleeping. In other words, the
* background thread is always running, but whether or not a new task
* to check for new files is created is determined by this boolean value.
*
* @return runATT If true, a new AddTracksTask object will be run when the
* BackgroundMonitor thread is finished sleeping.
*/
public boolean isRunning(){
return runATT;
}
/**
* Returns the amount of time in hours between checks for changed folders.
*
* @return Returns the amount of time in hours between checks for changed
* folders.
*/
public int getTimeHoursDelay() {
return (int)timeDelay / 60 / 60 / 1000;
}
@Override
public void run(){
while(true){
try{
if(runATT && a == null ||
runATT && a != null && a.isDone()){
runAddTracksTask();
}
Thread.sleep(timeDelay);
//Thread.sleep(40000L); //FIXME turn off
//Thread.sleep(5000L); //FIXME turn off
}
catch(InterruptedException e){
log.log(Level.SEVERE, this.getClass().getName(), e);
}
}
}
private void runAddTracksTask(){
if(!SysTrayMain.OPTIONS.isReady()){
return;
}
a = new AddTracksTask(false); //dont monitor progress
a.addPropertyChangeListener(new PropertyChangeListener(){
@Override
public void propertyChange(PropertyChangeEvent event){
if(status != null && a.getState() == StateValue.DONE){
try{
if(!a.isCancelled() && a.isDone()){
int numAdded = a.get();
if(numAdded > 0){
status.setBackgroundText(numAdded +
" new track(s) have been added to" +
" iTunes. Check the\n" +
OutputStringArrayList.ADDED_TRACKS_FILENAME +
"\nfile for new track(s).");
}
if(runATT){
setNextRun();
dispatchBackgroundMonitorStatusEvents(BackgroundMonitorStatusEvent.RUNNING,
BackgroundMonitorStatusEvent.NON_PROGRESS_EVENT);
}
else{
setNextRun(null);
dispatchBackgroundMonitorStatusEvents(BackgroundMonitorStatusEvent.STOPPED,
BackgroundMonitorStatusEvent.NON_PROGRESS_EVENT);
}
}
}
catch(ExecutionException e){
log.log(Level.SEVERE, this.getClass().getName(), e);
}
catch(InterruptedException e){
log.log(Level.SEVERE, this.getClass().getName(), e);
}
}
else if("progress".equals(event.getPropertyName())){
dispatchBackgroundMonitorStatusEvents(BackgroundMonitorStatusEvent.CHECKING_NEW_TRACKS,
(Integer)event.getNewValue());
}
}
});
a.execute();
}
/**
* Returns a Data object that details the next check for new tracks.
*
* @return The date when the next background monitor add tracks task will
* be run. This is a rough estimate as the Thread simply sleeps until the
* next run time.
*/
public Date getNextRunTime(){
return nextRunTime;
}
private void setNextRun(){
Calendar cal = Calendar.getInstance();
long nextRunMilli = cal.getTimeInMillis() + timeDelay;
Date nextRunDate = new Date(nextRunMilli);
setNextRun(nextRunDate);
}
private void setNextRun(Date nextRunDate){
this.nextRunTime = nextRunDate;
}
private String getNotDirsPaths(ArrayList<File> notDirs){
String dirs = "";
for(int i=0; i<notDirs.size(); i++){
dirs += notDirs.get(i).getAbsolutePath();
if(i != notDirs.size() - 1){
dirs += "\n";
}
}
return dirs;
}
/**
* Adds a new BackgroundMonitorStatusListener object to the background monitor
* object.
*
* @param listener The listener to add to the background monitor object.
*/
public void addBackgroundMonitorStatusListener(BackgroundMonitorStatusListener listener){
listeners.add(listener);
}
/**
* Removes the listener from the BackgroundMonitor object.
*
* @param listener The listener to remove from the BackgroundMonitor object.
*/
public void removeBackgroundMonitorStatusListener(BackgroundMonitorStatusListener listener){
listeners.remove(listener);
}
/**
* Alerts all listeners to a change in the BackgroundMonitor object.
*
* @param eventType The type of event triggered.
* @see BackgroundMonitorStatusEvent
*/
private void dispatchBackgroundMonitorStatusEvents(int eventType, int progress){
BackgroundMonitorStatusEvent e = new BackgroundMonitorStatusEvent(this, eventType, progress, nextRunTime);
for(int i=0; i<listeners.size(); i++){
listeners.get(i).statusChange(e);
}
}
}
|