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.SFTPConfiguration; import org.wavescale.sourcesync.logger.EventDataLogger; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; 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 SFTPFileSynchronizer extends FileSynchronizer { public static final String SSH_KNOWN_HOSTS = Paths.get(System.getProperty("user.home"), ".ssh", "known_hosts").toString(); private final JSch jsch; private Session session; /** * Build a file synchronizer from general info contained by <b>connectionInfo</b> param. * * @param connectionInfo a {@link org.wavescale.sourcesync.config.SFTPConfiguration} instance * containing session info like hostname, user, password, etc... * @param project a {@link com.intellij.openapi.project.Project} instance used to gather project relative * metadata like name, absoulte path, etc... * @param indicator used to report progress on upload process. */ public SFTPFileSynchronizer(@NotNull SFTPConfiguration 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 { SFTPConfiguration configuration = (SFTPConfiguration) 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(); String finalSourcePath = new File(getProject().getBasePath(), sourcePath).getAbsolutePath(); String remotePath = new File(this.getConnectionInfo().getRootPath(), destinationPath).getPath(); String[] dirsToCreate = Utils.splitPath(destinationPath); ChannelSftp channelSftp; try { channelSftp = (ChannelSftp) this.session.openChannel("sftp"); channelSftp.connect(); channelSftp.cd(Utils.getUnixPath(this.getConnectionInfo().getRootPath())); } catch (JSchException e) { EventDataLogger.logError(e.toString(), this.getProject()); return; } catch (SftpException e) { EventDataLogger.logError("Remote dir <b>" + this.getConnectionInfo().getRootPath() + "</b> might not exist or you don't have permission on this path!", this.getProject()); return; } // first try to create the path where this must be uploaded for (String dirToCreate : dirsToCreate) { try { channelSftp.mkdir(dirToCreate); } catch (SftpException e) { // this dir probably exist so just ignore } try { channelSftp.cd(dirToCreate); } catch (SftpException e) { // probably it doesn't exist or maybe no permission EventDataLogger.logError("Remote dir <b>" + remotePath + "</b> might not exist or you don't have permission on this path!", this.getProject()); return; } } // upload file File toUpload = new File(finalSourcePath); SftpProgressMonitor progressMonitor = new SftpMonitor(toUpload.length()); try { channelSftp.put(new FileInputStream(toUpload), toUpload.getName(), progressMonitor, ChannelSftp.OVERWRITE); if (preserveTimestamp) { SftpATTRS sftpATTRS = channelSftp.lstat(toUpload.getName()); int lastAcc = sftpATTRS.getATime(); // this is a messed method: if lastModified is greater than Integer.MAX_VALUE // then timestamp will not be ok. sftpATTRS.setACMODTIME(lastAcc, new Long(toUpload.lastModified() / 1000).intValue()); channelSftp.setStat(toUpload.getName(), sftpATTRS); } } catch (SftpException e) { EventDataLogger.logWarning(e.toString(), getProject()); } catch (FileNotFoundException e) { EventDataLogger.logWarning(e.toString(), getProject()); } channelSftp.disconnect(); } private class SftpMonitor implements SftpProgressMonitor { final double totalLength; long totalUploaded; public SftpMonitor(long totalLength) { this.totalLength = totalLength + 0.0; this.totalUploaded = 0; } @Override public void init(int opcode, String src, String dest, long max) { File remoteFile = new File(dest); if (SftpProgressMonitor.PUT == opcode) { SFTPFileSynchronizer.this.getIndicator().setText("Uploading...[" + remoteFile.getName() + "]"); SFTPFileSynchronizer.this.getIndicator().setIndeterminate(false); } } @Override public boolean count(long count) { totalUploaded += count; SFTPFileSynchronizer.this.getIndicator().setFraction(totalUploaded / totalLength); // false will kill the upload return true; } @Override public void end() { SFTPFileSynchronizer.this.getIndicator().setFraction(1.0); } } }