package com.mobilesorcery.sdk.deployment.internal.ftp; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.net.ProtocolCommandEvent; import org.apache.commons.net.ProtocolCommandListener; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SubProgressMonitor; import com.mobilesorcery.sdk.core.CascadingProperties; import com.mobilesorcery.sdk.core.CommandLineExecutor; import com.mobilesorcery.sdk.core.DefaultPackager; import com.mobilesorcery.sdk.core.IBuildResult; import com.mobilesorcery.sdk.core.IBuildSession; import com.mobilesorcery.sdk.core.IBuildVariant; import com.mobilesorcery.sdk.core.MoSyncBuilder; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.deployment.IDeploymentStrategy; import com.mobilesorcery.sdk.profiles.IProfile; public class FTPDeploymentStrategy implements IDeploymentStrategy { private final class TraceListener implements ProtocolCommandListener { private boolean active = true; @Override public void protocolReplyReceived(ProtocolCommandEvent event) { if (!active) { return; } System.err.println("<- " + event.getMessage().trim()); } @Override public void protocolCommandSent(ProtocolCommandEvent event) { sent(event.getMessage().trim()); } public void setActive(boolean active) { this.active = active; } public void sent(String msg) { if (!active) { return; } System.err.println("->" + msg); } } public static final String FACTORY_ID = "FTP"; private String host; private String password; private String username; private String remotePath; public FTPDeploymentStrategy() { remotePath = "%project-name%/%vendor%/%profile%/%package%"; } public void setHost(String host) { this.host = host; } public String getHost() { return host; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setPassword(String password) { this.password = password; } @Override public void deploy(MoSyncProject project, List<IProfile> profiles, IProgressMonitor monitor) throws Exception { monitor.beginTask("", profiles.size()); int i = 0; FTPClient ftp = connect(); try { for (IProfile profile : profiles) { i++; if (monitor.isCanceled()) { return; } monitor.setTaskName(MessageFormat.format( "Deploying {0} of {1} packages", i, profiles.size())); IBuildResult result = buildBeforeDeploy(project, profile, new NullProgressMonitor()); monitor.subTask("Uploading"); deploy(ftp, project, profile, result, new SubProgressMonitor( monitor, 1)); } } finally { disconnect(ftp); } } private void disconnect(FTPClient ftp) throws IOException { if (ftp.isConnected()) { ftp.logout(); } ftp.disconnect(); } private FTPClient connect() throws IOException { FTPClient ftp = new FTPClient(); ftp.connect(host); int reply = ftp.getReplyCode(); TraceListener listener = new TraceListener(); ftp.addProtocolCommandListener(listener); if (!FTPReply.isPositiveCompletion(reply)) { throw new IOException("FTP server refused connection."); } listener.setActive(false); listener.sent("*** LOGIN TRACE WITHHELD ***"); if (!ftp.login(username, password)) { throw new IOException(MessageFormat.format( "Could not login to ftp server {0}", host)); } listener.setActive(true); ftp.enterLocalPassiveMode(); assertOk(ftp.setFileType(FTP.BINARY_FILE_TYPE), "Could not set file type to binary on FTP server"); return ftp; } private IBuildResult buildBeforeDeploy(MoSyncProject project, IProfile profile, IProgressMonitor monitor) throws CoreException { IBuildVariant variant = MoSyncBuilder.createVariant(project, profile); IBuildSession session = MoSyncBuilder.createDefaultBuildSession(variant); IBuildResult buildResult = new MoSyncBuilder().build(project .getWrappedProject(), session, variant, null, monitor); return buildResult; } public void deploy(FTPClient ftp, MoSyncProject project, IProfile profile, IBuildResult result, IProgressMonitor monitor) throws Exception { InputStream localInputStream = null; try { IPath remotePath = getRemoteLocation(project, profile, result); localInputStream = createInputStream(project, profile, result); IPath changeDir = remotePath.removeLastSegments(1); ftp.changeWorkingDirectory("/"); makeOrChangeDir(ftp, changeDir); monitor.beginTask("Uploading", 1); assertOk(ftp.storeFile(remotePath.lastSegment(), localInputStream), "Could not store file"); } catch (IOException e) { throw new Exception("Could not deploy: " + e.getMessage(), e); } finally { Util.safeClose(localInputStream); monitor.done(); } } private void makeOrChangeDir(FTPClient ftp, IPath path) throws IOException { String[] segments = path.segments(); for (int i = 0; i < segments.length; i++) { if (!ftp.changeWorkingDirectory(segments[i])) { assertOk(ftp.makeDirectory(segments[i]), MessageFormat.format( "Could not create directory {0}", path.toString())); assertOk(ftp.changeWorkingDirectory(segments[i]), "Could not change directory"); } } } private void assertOk(boolean cond, String errormsg) throws IOException { if (!cond) { throw new IOException(errormsg); } } public IPath getRemoteLocation(MoSyncProject project, IProfile profile, IBuildResult result) throws IOException { String vendorName = profile.getVendor().getName(); String profileName = profile.getName(); String projectName = project.getName(); String packageName = getLocalFile(result).getName(); Map<String, String> properties = new HashMap<String, String>(); properties.put(DefaultPackager.PROJECT_NAME, projectName); properties.put(DefaultPackager.VENDOR_NAME, vendorName); properties.put(DefaultPackager.PROFILE_NAME, profileName); properties.put("package", packageName); String remotePath = CommandLineExecutor.replace(getRemotePath(), new CascadingProperties(new Map[] { properties })); return new Path(remotePath); } public String getRemotePath() { return remotePath; } public void setRemotePath(String remotePath) { this.remotePath = remotePath; } private File getLocalFile(IBuildResult result) throws IOException { List<File> files = result.getBuildResult().get(IBuildResult.MAIN); if (files == null || files.isEmpty() || !files.get(0).exists()) { throw new IOException("No package built/found"); } return files.get(0); } private InputStream createInputStream(MoSyncProject project, IProfile profile, IBuildResult result) throws IOException { return new FileInputStream(getLocalFile(result)); } @Override public String getFactoryId() { return FACTORY_ID; } public String getPassword() { return password; } }