package org.goko.core.execution.monitor.executionpart; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.time.DurationFormatUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.swt.SWT; import org.goko.common.bindings.AbstractController; import org.goko.core.common.exception.GkException; import org.goko.core.common.measure.quantity.Time; import org.goko.core.common.measure.quantity.TimeUnit; import org.goko.core.execution.IGCodeExecutionTimeService; import org.goko.core.gcode.execution.ExecutionQueue; import org.goko.core.gcode.execution.ExecutionQueueType; import org.goko.core.gcode.execution.ExecutionState; import org.goko.core.gcode.execution.ExecutionToken; import org.goko.core.gcode.execution.ExecutionTokenState; import org.goko.core.gcode.execution.IExecutionQueue; import org.goko.core.gcode.service.IExecutionQueueListener; import org.goko.core.gcode.service.IExecutionService; import org.goko.core.gcode.service.IGCodeExecutionListener; import org.goko.core.log.GkLog; /** * Part displaying execution state and allowing to control it * * @author Psyko */ public class ExecutionPartController extends AbstractController<ExecutionPartModel> implements IGCodeExecutionListener<ExecutionTokenState, ExecutionToken<ExecutionTokenState>>, IExecutionQueueListener<ExecutionTokenState, ExecutionToken<ExecutionTokenState>>{ private static final GkLog LOG = GkLog.getLogger(ExecutionPartController.class); /** The execution service */ @Inject private IExecutionService<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> executionService; /** The optional execution time evaluation service */ @Inject @Optional private IGCodeExecutionTimeService executionTimeService; /** Job for execution time estimation */ private Job executionUpdater; /** Map of execution time by token id */ private Map<Integer, Time> executionTimeByToken; /** Constructor */ public ExecutionPartController() { super(new ExecutionPartModel()); } /** (inheritDoc) * @see org.goko.common.bindings.AbstractController#initialize() */ @Override public void initialize() throws GkException { executionService.addExecutionListener(ExecutionQueueType.DEFAULT, this); executionService.addExecutionListener(ExecutionQueueType.SYSTEM, this); executionService.addExecutionQueueListener(this); updateTokenQueueData(); updateButtonState(); executionTimeByToken = new HashMap<Integer, Time>(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionStart(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionStart(ExecutionToken<ExecutionTokenState> token) throws GkException { this.getDataModel().setCompletedLineCount(0); this.getDataModel().setTokenLineCount(token.getLineCount()); this.getDataModel().setLineCompleteInCurrentToken(0); updateButtonState(); updateCompletedLineCount(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionComplete() */ @Override public void onQueueExecutionComplete() throws GkException { updateButtonState(); this.getDataModel().setExecutionTimerActive(false); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionStart() */ @Override public void onQueueExecutionStart() throws GkException { updateEstimatedExecutionTime(); updateButtonState(); this.getDataModel().setExecutionQueueStartDate(new Date()); this.getDataModel().setLineCompleteFromCompleteToken(0); this.getDataModel().setExecutionTimerActive(true); updateQueueExecutionState(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionCanceled(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionCanceled(ExecutionToken<ExecutionTokenState> token) throws GkException { updateButtonState(); updateQueueExecutionState(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onQueueExecutionCanceled() */ @Override public void onQueueExecutionCanceled() throws GkException { updateButtonState(); updateQueueExecutionState(); this.getDataModel().setExecutionTimerActive(false); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionPause(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionPause(ExecutionToken<ExecutionTokenState> token) throws GkException { updateButtonState(); updateQueueExecutionState(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionResume(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionResume(ExecutionToken<ExecutionTokenState> token) throws GkException { updateButtonState(); updateQueueExecutionState(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeTokenExecutionListener#onExecutionComplete(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onExecutionComplete(ExecutionToken<ExecutionTokenState> token) throws GkException { getDataModel().setLineCompleteInCurrentToken(0); updateTokenQueueData(); updateButtonState(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeLineExecutionListener#onLineStateChanged(org.goko.core.gcode.execution.IExecutionToken, java.lang.Integer) */ @Override public void onLineStateChanged(ExecutionToken<ExecutionTokenState> token, Integer idLine) throws GkException { LOG.info("onLineStateChanged"+idLine); if(executionService.getExecutionState() == ExecutionState.RUNNING || executionService.getExecutionState() == ExecutionState.PAUSED || executionService.getExecutionState() == ExecutionState.ERROR ){ getDataModel().setLineCompleteInCurrentToken(token.getLineCountByState(ExecutionTokenState.EXECUTED)); }else if(token.getState() == ExecutionState.COMPLETE){ getDataModel().setLineCompleteInCurrentToken(0); } updateCompletedLineCount(); } /** * Updates the total number of completed lines */ protected void updateCompletedLineCount(){ int executedLineCount = getDataModel().getLineCompleteInCurrentToken() + getDataModel().getLineCompleteFromCompleteToken(); this.getDataModel().setCompletedLineCount(executedLineCount); } /** * Starts the execution queue * @throws GkException GkException */ public void beginQueueExecution() throws GkException { executionService.beginQueueExecution(ExecutionQueueType.DEFAULT); this.getDataModel().setExecutionQueueStartDate(new Date()); this.getDataModel().setExecutionTimerActive(true); } /** * Pauses the execution queue * @throws GkException GkException */ public void pauseResumeQueueExecution() throws GkException { if(executionService.getExecutionState() == ExecutionState.PAUSED){ executionService.resumeQueueExecution(); }else if(executionService.getExecutionState() == ExecutionState.RUNNING){ executionService.pauseQueueExecution(); } } /** * Start or resume the execution queue * @throws GkException GkException */ public void startResumeQueueExecution() throws GkException { if(executionService.getExecutionState() == ExecutionState.PAUSED){ executionService.resumeQueueExecution(); }else{ executionService.beginQueueExecution(ExecutionQueueType.DEFAULT); } } /** * Start or resume the execution queue * @throws GkException GkException */ public void pauseStopQueueExecution() throws GkException { if(executionService.getExecutionState() == ExecutionState.PAUSED){ executionService.stopQueueExecution(); }else if(executionService.getExecutionState() == ExecutionState.RUNNING){ executionService.pauseQueueExecution(); } } /** * Stops the execution queue * @throws GkException GkException */ public void stopQueueExecution() throws GkException { executionService.stopQueueExecution(); } public ExecutionState getExecutionState() throws GkException{ return executionService.getExecutionState(); } /** * Update the state of the buttons according to the execution state * @throws GkException GkException */ private void updateButtonState(){ try{ boolean buttonStopEnabled = false; boolean buttonStartEnabled= false; boolean buttonPauseEnabled = false; switch(executionService.getExecutionState()){ case IDLE: case STOPPED: case COMPLETE: buttonStartEnabled = true; buttonPauseEnabled = false; buttonStopEnabled = false; break; case PAUSED: case ERROR: case FATAL_ERROR: case RUNNING: buttonStartEnabled = false; buttonPauseEnabled = true; buttonStopEnabled = true; break; } if(getCurrentExecutionQueue() != null){ if(CollectionUtils.isEmpty(getCurrentExecutionQueue().getExecutionToken())){ buttonStartEnabled = false; }else{ buttonStartEnabled = !isErrorInQueue(getCurrentExecutionQueue()); } } getDataModel().setButtonStartEnabled(buttonStartEnabled); getDataModel().setButtonPauseEnabled(buttonPauseEnabled); getDataModel().setButtonStopEnabled( buttonStopEnabled); }catch(GkException e){ LOG.error(e); getDataModel().setButtonStartEnabled(false); getDataModel().setButtonPauseEnabled(false); getDataModel().setButtonStopEnabled( true ); } } /** * Detects error in the given queue * @param iExecutionQueue the queue to check * @return <code>true</code> if there is any error in the queue, <code>false</code> otherwise * @throws GkException GkException */ private boolean isErrorInQueue(IExecutionQueue<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> iExecutionQueue) throws GkException{ if(iExecutionQueue == null){ return false; } List<ExecutionToken<ExecutionTokenState>> tokens = iExecutionQueue.getExecutionToken(); if(CollectionUtils.isNotEmpty(tokens)){ for (ExecutionToken<ExecutionTokenState> executionToken : tokens) { if(executionToken.hasErrors()){ return true; } } } return false; } /** * Update the data about the execution of the queue * @throws GkException GkException */ private void updateQueueExecutionState() throws GkException{ if(executionService.getExecutionState() == ExecutionState.RUNNING){ getDataModel().setProgressBarState(SWT.NORMAL); getDataModel().setTokenProgressBarState(SWT.NORMAL); }else if(executionService.getExecutionState() == ExecutionState.PAUSED){ getDataModel().setProgressBarState(SWT.PAUSED); getDataModel().setTokenProgressBarState(SWT.PAUSED); }else if(executionService.getExecutionState() == ExecutionState.STOPPED){ getDataModel().setProgressBarState(SWT.ERROR); getDataModel().setTokenProgressBarState(SWT.ERROR); } } /** * Update the estimated execution time * @throws GkException GkException */ private void updateEstimatedExecutionTime() throws GkException{ if(executionUpdater == null || executionUpdater.getState() == Job.NONE){ scheduleExecutionTimeEstimationJob(); } } private void scheduleExecutionTimeEstimationJob() throws GkException{ scheduleExecutionTimeEstimationJob(null); } private void scheduleExecutionTimeEstimationJob(final Integer idToken) { executionUpdater = new Job("Updating execution time..."){ /** (inheritDoc) * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IStatus run(IProgressMonitor monitor) { if(executionTimeService != null){ try { List<ExecutionToken<ExecutionTokenState>> lstToken = getCurrentExecutionQueue().getExecutionToken(); Time estimatedTime = Time.ZERO; if(CollectionUtils.isNotEmpty(lstToken)){ SubMonitor subMonitor = SubMonitor.convert(monitor,"Computing time...", lstToken.size()); // Refresh the unitary times first for (ExecutionToken<ExecutionTokenState> executionToken : lstToken) { Time tokenTime = executionTimeByToken.get(executionToken.getId()); if(tokenTime == null || idToken == null || ObjectUtils.equals(idToken, executionToken.getId())){ tokenTime = executionTimeService.evaluateExecutionTime(executionToken.getGCodeProvider()); executionTimeByToken.put(executionToken.getId(), tokenTime); } subMonitor.worked(1); estimatedTime = estimatedTime.add(tokenTime); } subMonitor.done(); } long estimatedMs = (long)estimatedTime.doubleValue(TimeUnit.MILLISECOND); ExecutionPartController.this.getDataModel().setEstimatedTimeString(DurationFormatUtils.formatDuration(estimatedMs, "HH:mm:ss")); } catch (GkException e) { LOG.error(e); } } return Status.OK_STATUS; } }; executionUpdater.setUser(true); executionUpdater.schedule(100); } /** * Update the data about the execution queue * @throws GkException GkException */ private void updateTokenQueueData() { try{ List<ExecutionToken<ExecutionTokenState>> lstToken = getCurrentExecutionQueue().getExecutionToken(); int tokenCount = CollectionUtils.size(lstToken); this.getDataModel().setTotalTokenCount(tokenCount); int totalTokenCount = 0; int completedTokenCount = 0; int totalLineCount = 0; int completedLineCount = 0; for (ExecutionToken<ExecutionTokenState> executionToken : lstToken) { totalTokenCount += 1; totalLineCount += executionToken.getLineCount(); if(executionToken.getState() == ExecutionState.COMPLETE){ completedTokenCount += 1; completedLineCount += executionToken.getLineCount(); } } this.getDataModel().setCompletedTokenCount(completedTokenCount); this.getDataModel().setTotalTokenCount(totalTokenCount); this.getDataModel().setTotalLineCount(totalLineCount); this.getDataModel().setLineCompleteFromCompleteToken(completedLineCount); updateCompletedLineCount(); updateEstimatedExecutionTime(); }catch(GkException e){ LOG.error(e); } } private ExecutionQueue<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> getCurrentExecutionQueue() throws GkException{ ExecutionQueue<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> currentQueue = executionService.findRunningExecutionQueue(); if(executionService.getExecutionState() == ExecutionState.COMPLETE || executionService.getExecutionState() == ExecutionState.IDLE || currentQueue == null){ currentQueue = executionService.getExecutionQueue(ExecutionQueueType.DEFAULT); } return currentQueue; } /** * @return the executionService */ public IExecutionService<ExecutionTokenState, ExecutionToken<ExecutionTokenState>> getExecutionService() { return executionService; } /** (inheritDoc) * @see org.goko.core.gcode.service.IExecutionQueueListener#onTokenCreate(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onTokenCreate(ExecutionToken<ExecutionTokenState> token) { updateButtonState(); updateTokenQueueData(); scheduleExecutionTimeEstimationJob(token.getId()); } /** (inheritDoc) * @see org.goko.core.gcode.service.IExecutionQueueListener#onTokenDelete(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onTokenDelete(ExecutionToken<ExecutionTokenState> token) { updateButtonState(); updateTokenQueueData(); executionTimeByToken.remove(token.getId()); scheduleExecutionTimeEstimationJob(token.getId()); } /** (inheritDoc) * @see org.goko.core.gcode.service.IExecutionQueueListener#onTokenUpdate(org.goko.core.gcode.execution.IExecutionToken) */ @Override public void onTokenUpdate(ExecutionToken<ExecutionTokenState> token) { updateButtonState(); updateTokenQueueData(); scheduleExecutionTimeEstimationJob(token.getId()); } }