package org.wavescale.sourcesync.synchronizer;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.jcraft.jsch.*;
import org.jetbrains.annotations.NotNull;
import org.wavescale.sourcesync.api.FileSynchronizer;
import org.wavescale.sourcesync.api.Utils;
import org.wavescale.sourcesync.config.SCPConfiguration;
import org.wavescale.sourcesync.logger.BalloonLogger;
import org.wavescale.sourcesync.logger.EventDataLogger;
import java.io.*;
import java.nio.file.Paths;
/**
* ****************************************************************************
* Copyright (c) 2005-2014 Faur Ioan-Aurel. *
* All rights reserved. This program and the accompanying materials *
* are made available under the terms of the MIT License *
* which accompanies this distribution, and is available at *
* http://opensource.org/licenses/MIT *
* *
* For any issues or questions send an email at: fioan89@gmail.com *
* *****************************************************************************
*/
public class SCPFileSynchronizer extends FileSynchronizer {
public static final String SSH_KNOWN_HOSTS = Paths.get(System.getProperty("user.home"), ".ssh", "known_hosts").toString();
private JSch jsch;
private Session session;
public SCPFileSynchronizer(@NotNull SCPConfiguration connectionInfo, @NotNull Project project, @NotNull ProgressIndicator indicator) {
super(connectionInfo, project, indicator);
this.jsch = new JSch();
this.getIndicator().setIndeterminate(true);
}
@Override
public boolean connect() {
if (!isConnected()) {
try {
initSession();
session.setPassword(this.getConnectionInfo().getUserPassword());
this.session.setConfig("StrictHostKeyChecking", "no");
this.session.connect();
this.setConnected(true);
return true;
} catch (JSchException e) {
EventDataLogger.logWarning(e.toString(), this.getProject());
return false;
}
}
return true;
}
private void initSession() throws JSchException {
SCPConfiguration configuration = (SCPConfiguration) this.getConnectionInfo();
session = this.jsch.getSession(this.getConnectionInfo().getUserName(), this.getConnectionInfo().getHost(),
this.getConnectionInfo().getPort());
if (configuration.isPasswordlessSSHSelected()) {
session.setConfig("PreferredAuthentications", "publickey");
try {
Utils.createFile(SSH_KNOWN_HOSTS);
} catch (IOException e) {
EventDataLogger.logError("Could not identify nor create the ssh known hosts file at " + SSH_KNOWN_HOSTS + ". The returned error is:" + e.getMessage(), this.getProject());
}
this.jsch.setKnownHosts(SSH_KNOWN_HOSTS);
// add private key and passphrase if exists
if (configuration.isPasswordlessWithPassphrase()) {
this.jsch.addIdentity(configuration.getCertificatePath(), configuration.getUserPassword());
} else {
this.jsch.addIdentity(configuration.getCertificatePath());
}
} else {
session.setPassword(this.getConnectionInfo().getUserPassword());
}
}
@Override
public void disconnect() {
if (this.session != null) {
this.session.disconnect();
this.setConnected(false);
}
}
/**
* Uploads the given file to the remote target.
*
* @param sourcePath a <code>String</code> representing a file path to be uploaded. This is a relative path
* to project base path.
* @param destinationPath a <code>String</code> representing a location path on the remote target
* where the source will be uploaded.
*/
@Override
public void syncFile(String sourcePath, String destinationPath) {
boolean preserveTimestamp = this.getConnectionInfo().isPreserveTime();
// exec 'scp -t rfile' remotely
String finalSourcePath = Utils.buildUnixPath(getProject().getBasePath(), sourcePath);
String remotePath = Utils.buildUnixPath(this.getConnectionInfo().getRootPath(), destinationPath);
try {
String command = "scp " + (preserveTimestamp ? "-p" : "") + " -t -C " + remotePath;
Channel channel = this.session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
// get I/O streams for remote scp
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();
channel.connect();
if (checkAck(in) != 0) {
return;
}
File _lfile = new File(finalSourcePath);
this.getIndicator().setIndeterminate(false);
this.getIndicator().setText("Uploading...[" + _lfile.getName() + "]");
if (preserveTimestamp) {
command = "T " + (_lfile.lastModified() / 1000) + " 0";
// The access time should be sent here,
// but it is not accessible with JavaAPI ;-<
command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
out.write(command.getBytes());
out.flush();
if (checkAck(in) != 0) {
return;
}
}
// send "C0644 filesize filename", where filename should not include '/'
long filesize = _lfile.length();
command = "C0644 " + filesize + " ";
if (finalSourcePath.lastIndexOf('/') > 0) {
command += finalSourcePath.substring(finalSourcePath.lastIndexOf('/') + 1);
} else {
command += finalSourcePath;
}
command += "\n";
out.write(command.getBytes());
out.flush();
if (checkAck(in) != 0) {
return;
}
// send content of finalSourcePath
FileInputStream fis = new FileInputStream(finalSourcePath);
double totalUploaded = 0.0;
byte[] buf = new byte[1024];
while (true) {
int len = fis.read(buf, 0, buf.length);
if (len <= 0) break;
out.write(buf, 0, len); //out.flush();
totalUploaded += len;
this.getIndicator().setFraction(totalUploaded / filesize);
}
fis.close();
// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();
if (checkAck(in) != 0) {
return;
}
out.close();
channel.disconnect();
} catch (IOException e) {
EventDataLogger.logWarning(e.toString(), getProject());
} catch (JSchException e) {
EventDataLogger.logWarning(e.toString(), getProject());
}
}
private int checkAck(InputStream in) throws IOException {
int b = in.read();
// b may be 0 for success,
// 1 for error,
// 2 for fatal error,
// -1
if (b == 0) return b;
if (b == -1) return b;
if (b == 1 || b == 2) {
StringBuilder sb = new StringBuilder();
int c;
do {
c = in.read();
sb.append((char) c);
}
while (c != '\n');
if (b == 1) { // error
BalloonLogger.logBalloonError(sb.toString(), this.getProject());
EventDataLogger.logError(sb.toString(), getProject());
}
if (b == 2) { // fatal error
BalloonLogger.logBalloonError(sb.toString(), this.getProject());
EventDataLogger.logError(sb.toString(), getProject());
}
}
return b;
}
}