de.innot.avreclipse.ui.actions.UploadProjectAction.java Source code

Java tutorial

Introduction

Here is the source code for de.innot.avreclipse.ui.actions.UploadProjectAction.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Thomas Holland - initial API and implementation
 *******************************************************************************/
package de.innot.avreclipse.ui.actions;

import java.io.File;
import java.text.MessageFormat;
import java.util.List;

import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.managedbuilder.core.IConfiguration;
import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo;
import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionDelegate;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionDelegate;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.progress.UIJob;

import de.innot.avreclipse.AVRPlugin;
import de.innot.avreclipse.core.avrdude.AVRDudeAction;
import de.innot.avreclipse.core.avrdude.AVRDudeException;
import de.innot.avreclipse.core.avrdude.AVRDudeSchedulingRule;
import de.innot.avreclipse.core.avrdude.BaseBytesProperties;
import de.innot.avreclipse.core.avrdude.ProgrammerConfig;
import de.innot.avreclipse.core.properties.AVRDudeProperties;
import de.innot.avreclipse.core.properties.AVRProjectProperties;
import de.innot.avreclipse.core.properties.ProjectPropertyManager;
import de.innot.avreclipse.core.toolinfo.AVRDude;
import de.innot.avreclipse.core.toolinfo.fuses.FuseType;
import de.innot.avreclipse.core.util.AVRMCUidConverter;
import de.innot.avreclipse.mbs.BuildMacro;
import de.innot.avreclipse.ui.dialogs.AVRDudeErrorDialogJob;

/**
 * @author Thomas Holland
 * @since 2.2
 * @since 2.3 Added optional delay between avrdude invocations
 * 
 */
public class UploadProjectAction extends ActionDelegate implements IWorkbenchWindowActionDelegate {

    private final static String TITLE_UPLOAD = "AVRDude Upload";

    private final static String SOURCE_BUILDCONFIG = "active build configuration";
    private final static String SOURCE_PROJECT = "project";

    private final static String MSG_NOPROJECT = "No AVR project selected";

    private final static String MSG_NOPROGRAMMER = "No Programmer has been set for the {0}.\n\n"
            + "Please select a Programmer in the project properties\n" + "(Properties -> AVRDude -> Programmer)";

    private final static String MSG_WRONGMCU = "AVRDude does not support the project target MCU [{0}]\n\n"
            + "Please select a different target MCU if you want to use AVRDude.\n"
            + "(Properties -> Target Hardware)";

    private final static String MSG_NOACTIONS = "The {0} has no options set to upload anything to the device.\n\n"
            + "Please select at least one item to upload (flash / eeprom / fuses / lockbits)";

    private final static String MSG_MISSING_FILE = "The file [{0}] for the {1} memory does not exist or is not readable\n\n"
            + "Maybe the project needs to be build first.";

    private final static String MSG_MISSING_FUSES_FILE = "The selected {0} file [{1}] does not exist or is not readable\n\n"
            + "Please select a different {0} source.\n" + "(Properties -> AVRDude -> {0}";

    private final static String MSG_INVALIDFUSEBYTE = "The {0} byte(s) to upload are for an {1} MCU, "
            + "which is not compatible with the {3} target MCU [{2}]\n\n" + "Please check the fuse byte settings.\n"
            + "(Properties -> AVRDude -> {0})";

    private final static String MSG_NEEDS_REBUILD = "A rebuild is required before upload.";

    private IProject fProject;

    /**
     * Constructor for this Action.
     */
    public UploadProjectAction() {
        super();
    }

    /**
     * @see IActionDelegate#selectionChanged(IAction, ISelection)
     */
    @Override
    public void selectionChanged(IAction action, ISelection selection) {

        // The user has selected a different Workbench object.
        // If it is an IProject we keep it.

        Object item;

        if (selection instanceof IStructuredSelection) {
            item = ((IStructuredSelection) selection).getFirstElement();
        } else {
            return;
        }
        if (item == null) {
            return;
        }
        IProject project = null;

        // See if the given is an IProject (directly or via IAdaptable)
        if (item instanceof IProject) {
            project = (IProject) item;
        } else if (item instanceof IResource) {
            project = ((IResource) item).getProject();
        } else if (item instanceof IAdaptable) {
            IAdaptable adaptable = (IAdaptable) item;
            project = (IProject) adaptable.getAdapter(IProject.class);
            if (project == null) {
                // Try ICProject -> IProject
                ICProject cproject = (ICProject) adaptable.getAdapter(ICProject.class);
                if (cproject == null) {
                    // Try ICElement -> ICProject -> IProject
                    ICElement celement = (ICElement) adaptable.getAdapter(ICElement.class);
                    if (celement != null) {
                        cproject = celement.getCProject();
                    }
                }
                if (cproject != null) {
                    project = cproject.getProject();
                }
            }
        }

        fProject = project;
    }

    /**
     * @see IActionDelegate#run(IAction)
     */
    @Override
    public void run(IAction action) {

        // Check that we have a AVR Project
        try {
            if (fProject == null || !fProject.hasNature("de.innot.avreclipse.core.avrnature")) {
                MessageDialog.openError(getShell(), TITLE_UPLOAD, MSG_NOPROJECT);
                return;
            }
        } catch (CoreException e) {
            // Log the Exception
            IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID, "Can't access project nature", e);
            AVRPlugin.getDefault().log(status);
        }

        // Get the active build configuration
        IManagedBuildInfo bi = ManagedBuildManager.getBuildInfo(fProject);
        IConfiguration activecfg = bi.getDefaultConfiguration();

        // Check if project needs to be rebuild (e.g. MCU type changed)
        if (activecfg.needsRebuild()) {
            MessageDialog.openError(getShell(), TITLE_UPLOAD, MSG_NEEDS_REBUILD);
            return;
        }

        // Get the avr properties for the active configuration
        AVRProjectProperties targetprops = ProjectPropertyManager.getPropertyManager(fProject)
                .getActiveProperties();

        // Check if the avrdude properties are valid.
        // if not the checkProperties() method will display an error message box
        if (!checkProperties(activecfg, targetprops)) {
            return;
        }

        // Everything is fine -> run avrdude
        runAVRDude(activecfg, targetprops);
    }

    /**
     * Check that the current properties are valid.
     * <p>
     * This method will check that:
     * <ul>
     * <li>there has been a Programmer selected</li>
     * <li>avrdude supports the selected MCU</li>
     * <li>there are some actions to perform</li>
     * <li>all source files exist</li>
     * <li>the fuse bytes (if uploaded) are valid for the target MCU</li>
     * </ul>
     * <p>
     * 
     * @param buildcfg
     *            The current build configuration
     * @param props
     *            The current Properties
     * @return <code>true</code> if everything is OK.
     */
    private boolean checkProperties(IConfiguration buildcfg, AVRProjectProperties props) {

        boolean perconfig = ProjectPropertyManager.getPropertyManager(fProject).isPerConfig();
        String source = perconfig ? SOURCE_BUILDCONFIG : SOURCE_PROJECT;

        // Check that a Programmer has been selected
        if (props.getAVRDudeProperties().getProgrammer() == null) {
            String message = MessageFormat.format(MSG_NOPROGRAMMER, source);
            MessageDialog.openError(getShell(), TITLE_UPLOAD, message);
            return false;
        }

        // Check that the MCU is valid for avrdude
        String mcuid = props.getMCUId();
        if (!AVRDude.getDefault().hasMCU(mcuid)) {
            String message = MessageFormat.format(MSG_WRONGMCU, AVRMCUidConverter.id2name(mcuid));
            MessageDialog.openError(getShell(), TITLE_UPLOAD, message);
            return false;
        }

        // Check that there is actually anything to upload
        List<String> actionlist = props.getAVRDudeProperties().getActionArguments(buildcfg, true);
        if (actionlist.size() == 0) {
            String message = MessageFormat.format(MSG_NOACTIONS, source);
            MessageDialog.openWarning(getShell(), TITLE_UPLOAD, message);
            return false;
        }

        // Check all referenced files
        // It would be cumbersome to go through all possible cases. Instead we
        // convert all action arguments back to AVRDudeActions and get the
        // filename from it.
        IPath invalidfile = null;
        String formemtype = null;
        for (String argument : actionlist) {
            AVRDudeAction action = AVRDudeAction.getActionForArgument(argument);
            String filename = action.getFilename();
            if (filename == null)
                continue;
            IPath rawfile = new Path(filename);
            IPath unresolvedfile = rawfile;
            IPath resolvedfile = rawfile;
            if (!rawfile.isAbsolute()) {
                // The filename is relative to the build folder. Get the build
                // folder and append our filename. Then resolve any macros
                unresolvedfile = buildcfg.getBuildData().getBuilderCWD().append(rawfile);
                resolvedfile = new Path(BuildMacro.resolveMacros(buildcfg, unresolvedfile.toString()));
            }
            File realfile = resolvedfile.toFile();
            if (!realfile.canRead()) {
                invalidfile = unresolvedfile;
                formemtype = action.getMemType().toString();
                break;
            }
        }
        if (invalidfile != null) {
            String message = MessageFormat.format(MSG_MISSING_FILE, invalidfile.toString(), formemtype);
            MessageDialog.openError(getShell(), TITLE_UPLOAD, message);
            return false;
        }

        // Check that the fuses and locks are valid (if they are to be uploaded)
        for (FuseType type : FuseType.values()) {

            if (props.getAVRDudeProperties().getBytesProperties(type, buildcfg).getWrite()) {
                BaseBytesProperties bytesproperties = props.getAVRDudeProperties().getBytesProperties(type,
                        buildcfg);
                if (bytesproperties.getMCUId() == null) {
                    // A non-existing file has been selected as source for the fuses
                    String message = MessageFormat.format(MSG_MISSING_FUSES_FILE, type.toString(),
                            bytesproperties.getFileNameResolved());
                    MessageDialog.openError(getShell(), TITLE_UPLOAD, message);
                    return false;
                }
                if (!bytesproperties.isCompatibleWith(props.getMCUId())) {
                    String fusesmcuid = AVRMCUidConverter.id2name(bytesproperties.getMCUId());
                    String propsmcuid = AVRMCUidConverter.id2name(props.getMCUId());
                    String message = MessageFormat.format(MSG_INVALIDFUSEBYTE, type.toString(), fusesmcuid,
                            propsmcuid, source);
                    MessageDialog.openError(getShell(), TITLE_UPLOAD, message);
                    return false;
                }
            }
        }

        // Everything is OK
        return true;
    }

    /**
     * Start the AVRDude UploadJob.
     * 
     * @param buildcfg
     *            The build configuration for resolving macros.
     * @param props
     *            The AVR properties for the project / the current configuration
     */
    private void runAVRDude(IConfiguration buildcfg, AVRProjectProperties props) {

        AVRDudeProperties avrdudeprops = props.getAVRDudeProperties();

        // get the list of normal (non-action) arguments
        List<String> optionargs = avrdudeprops.getArguments();

        // get a list of actions
        List<String> actionargs = avrdudeprops.getActionArguments(buildcfg, true);

        // Get the ProgrammerConfig in case we need to display an error
        // message
        ProgrammerConfig programmer = avrdudeprops.getProgrammer();

        // Set the working directory to the CWD of the active build config, so that
        // relative paths are resolved correctly.
        IPath cwdunresolved = buildcfg.getBuildData().getBuilderCWD();
        IPath cwd = new Path(BuildMacro.resolveMacros(buildcfg, cwdunresolved.toString()));
        Job uploadjob = new UploadJob(optionargs, actionargs, cwd, programmer);

        uploadjob.setRule(new AVRDudeSchedulingRule(programmer));
        uploadjob.setPriority(Job.LONG);
        uploadjob.setUser(true);

        uploadjob.schedule();

    }

    /**
     * The background Job to execute the requested avrdude commands.
     * 
     */
    private static class UploadJob extends Job {

        private final List<String> fOptions;
        private final List<String> fActions;
        private final IPath fCwd;
        private final ProgrammerConfig fProgrammerConfig;

        public UploadJob(List<String> options, List<String> actions, IPath cwd, ProgrammerConfig programmer) {
            super("AVRDude Upload");
            fOptions = options;
            fActions = actions;
            fCwd = cwd;
            fProgrammerConfig = programmer;
        }

        @Override
        public IStatus run(IProgressMonitor monitor) {

            try {
                monitor.beginTask("Running AVRDude", fActions.size());

                // init console. Clears the console and puts it on top.
                // AVRDude is forced to use the console, so the user will always
                // see the output, regardless of the "use console" flag.
                MessageConsole console = AVRPlugin.getDefault().getConsole("AVRDude");
                console.clearConsole();
                console.activate();

                AVRDude avrdude = AVRDude.getDefault();

                // Append all requested actions
                // The reason this is done here is because in earlier versions
                // of the plugin each action was send separately to avrdude to better
                // track the progress.
                // However some users complained that this slows the whole upload process down.
                // So now we sent all actions in one go, as the user can monitor the progress
                // in the console anyway.
                fOptions.addAll(fActions);
                monitor.subTask("Running AVRDude");

                // Now avrdude can be started.
                avrdude.runCommand(fOptions, new SubProgressMonitor(monitor, 1), true, fCwd, fProgrammerConfig);

            } catch (AVRDudeException ade) {
                // Show an Error message and exit
                Display display = PlatformUI.getWorkbench().getDisplay();
                if (display != null && !display.isDisposed()) {
                    UIJob messagejob = new AVRDudeErrorDialogJob(display, ade, fProgrammerConfig.getId());
                    messagejob.setPriority(Job.INTERACTIVE);
                    messagejob.schedule();
                    try {
                        messagejob.join(); // block until the dialog is closed.
                    } catch (InterruptedException e) {
                        // Don't care if the dialog is interrupted from outside.
                    }
                }
            } finally {
                monitor.done();
            }

            return Status.OK_STATUS;
        }
    }

    /**
     * Get the current Shell.
     * 
     * @return <code>Shell</code> of the active Workbench window.
     */
    private Shell getShell() {
        return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
     */
    public void init(IWorkbenchWindow window) {
        // TODO Auto-generated method stub

    }

}