cc.arduino.packages.uploaders.SSHUploader.java Source code

Java tutorial

Introduction

Here is the source code for cc.arduino.packages.uploaders.SSHUploader.java

Source

/*
 * This file is part of Arduino.
 *
 * Arduino 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 *
 * Copyright 2013 Arduino LLC (http://www.arduino.cc/)
 */

package cc.arduino.packages.uploaders;

import cc.arduino.CompilerUtils;
import cc.arduino.packages.BoardPort;
import cc.arduino.packages.Uploader;
import cc.arduino.packages.ssh.*;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import processing.app.BaseNoGui;
import processing.app.I18n;
import processing.app.PreferencesData;
import processing.app.debug.RunnerException;
import processing.app.debug.TargetPlatform;
import processing.app.helpers.PreferencesMap;
import processing.app.helpers.PreferencesMapException;
import processing.app.helpers.StringReplacer;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import static processing.app.I18n.tr;

public class SSHUploader extends Uploader {

    private static final Set<String> FILES_NOT_TO_COPY = Collections
            .unmodifiableSet(new HashSet<String>(Arrays.asList(".DS_Store", ".Trash", "Thumbs.db", "__MACOSX")));

    private final BoardPort port;

    public SSHUploader(BoardPort port) {
        this.port = port;
    }

    @Override
    public boolean requiresAuthorization() {
        return true;
    }

    @Override
    public String getAuthorizationKey() {
        return "runtime.pwd." + port.getAddress();
    }

    @Override
    public boolean uploadUsingPreferences(File sourcePath, String buildPath, String className,
            boolean usingProgrammer, List<String> warningsAccumulator)
            throws RunnerException, PreferencesMapException {
        if (usingProgrammer) {
            throw new RunnerException(tr("Network upload using programmer not supported"));
        }

        TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
        PreferencesMap prefs = PreferencesData.getMap();
        PreferencesMap boardPreferences = BaseNoGui.getBoardPreferences();
        if (boardPreferences != null) {
            prefs.putAll(boardPreferences);
        }
        String tool = prefs.getOrExcept("upload.tool");
        if (tool.contains(":")) {
            String[] split = tool.split(":", 2);
            targetPlatform = BaseNoGui.getCurrentTargetPlatformFromPackage(split[0]);
            tool = split[1];
        }
        prefs.putAll(targetPlatform.getTool(tool));

        boolean coreMissesRemoteUploadTool = targetPlatform.getTool(tool + "_remote").isEmpty();

        if (coreMissesRemoteUploadTool) {
            prefs.put("upload.pattern", "/usr/bin/run-avrdude /tmp/sketch.hex");
        } else {
            prefs.putAll(targetPlatform.getTool(tool + "_remote"));
        }

        prefs.put("build.path", buildPath);
        prefs.put("build.project_name", className);

        Session session = null;
        SCP scp = null;
        try {
            JSch jSch = new JSch();
            SSHClientSetupChainRing sshClientSetupChain = new SSHConfigFileSetup(new SSHPwdSetup());
            session = sshClientSetupChain.setup(port, jSch);

            session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");

            session.setUserInfo(new NoInteractionUserInfo(PreferencesData.get("runtime.pwd." + port.getAddress())));
            session.connect(30000);

            scp = new SCP(session);
            SSH ssh = new SSH(session);

            File mergedSketch = new File(buildPath, className + ".with_bootloader.hex");

            File sketchToCopy;
            if (!coreMissesRemoteUploadTool && mergedSketch.exists()) {
                sketchToCopy = mergedSketch;
            } else {
                sketchToCopy = new CompilerUtils().findCompiledSketch(prefs);
            }
            scpFiles(scp, ssh, sourcePath, sketchToCopy, warningsAccumulator);

            if (coreMissesRemoteUploadTool) {
                ssh.execSyncCommand("merge-sketch-with-bootloader.lua /tmp/sketch.hex", System.out, System.err);
            }

            return runUploadTool(ssh, prefs);
        } catch (JSchException e) {
            String message = e.getMessage();
            if (message.contains("Auth cancel") || message.contains("Auth fail")
                    || message.contains("authentication fail")) {
                return false;
            }
            if (e.getMessage().contains("Connection refused")) {
                throw new RunnerException(I18n.format(tr("Unable to connect to {0}"), port.getAddress()));
            }
            throw new RunnerException(e);
        } catch (Exception e) {
            throw new RunnerException(e);
        } finally {
            if (scp != null) {
                scp.close();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }

    private boolean runUploadTool(SSH ssh, PreferencesMap prefs) throws Exception {
        ssh.execSyncCommand("kill-bridge");

        if (verbose) {
            prefs.put("upload.verbose", prefs.getOrExcept("upload.params.verbose"));
        } else {
            prefs.put("upload.verbose", prefs.getOrExcept("upload.params.quiet"));
        }

        String pattern = prefs.getOrExcept("upload.pattern");
        String command = StringUtils.join(StringReplacer.formatAndSplit(pattern, prefs), " ");
        if (verbose) {
            System.out.println(command);
        }
        return ssh.execSyncCommand(command, System.out, System.err);
    }

    private void scpFiles(SCP scp, SSH ssh, File sourcePath, File sketch, List<String> warningsAccumulator)
            throws JSchException, IOException {
        String uploadedSketchFileName;
        if (sketch.getName().endsWith("hex")) {
            uploadedSketchFileName = "sketch.hex";
        } else {
            uploadedSketchFileName = "sketch.bin";
        }

        try {
            scp.open();
            scp.startFolder("tmp");
            scp.sendFile(sketch, uploadedSketchFileName);
            scp.endFolder();

            if (canUploadWWWFiles(sourcePath, ssh, warningsAccumulator)) {
                scp.startFolder("www");
                scp.startFolder("sd");
                scp.startFolder(sourcePath.getName());
                recursiveSCP(new File(sourcePath, "www"), scp);
                scp.endFolder();
                scp.endFolder();
                scp.endFolder();
            }
        } finally {
            scp.close();
        }
    }

    private boolean canUploadWWWFiles(File sourcePath, SSH ssh, List<String> warningsAccumulator)
            throws IOException, JSchException {
        File www = new File(sourcePath, "www");
        if (!www.exists() || !www.isDirectory()) {
            return false;
        }
        if (!www.canExecute()) {
            warningsAccumulator.add(I18n.format(tr("Problem accessing files in folder \"{0}\""), www));
            return false;
        }
        if (!ssh.execSyncCommand("special-storage-available")) {
            warningsAccumulator.add(tr("Problem accessing board folder /www/sd"));
            return false;
        }
        return true;
    }

    private void recursiveSCP(File from, SCP scp) throws IOException {
        File[] files = from.listFiles();
        if (files == null) {
            return;
        }

        for (File file : files) {
            if (!FILES_NOT_TO_COPY.contains(file.getName())) {
                if (file.isDirectory() && file.canExecute()) {
                    scp.startFolder(file.getName());
                    recursiveSCP(file, scp);
                    scp.endFolder();
                } else if (file.isFile() && file.canRead()) {
                    scp.sendFile(file);
                }
            }
        }
    }

    @Override
    public boolean burnBootloader() throws RunnerException {
        throw new RunnerException("Can't burn bootloader via SSH");
    }

}