aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java')
-rw-r--r--framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java2725
1 files changed, 2725 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
new file mode 100644
index 00000000..cef9dda0
--- /dev/null
+++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
@@ -0,0 +1,2725 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.tools.ant.taskdefs.optional.net;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPClientConfig;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.taskdefs.Delete;
+import org.apache.tools.ant.types.EnumeratedAttribute;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.selectors.SelectorUtils;
+import org.apache.tools.ant.util.FileUtils;
+import org.apache.tools.ant.util.RetryHandler;
+import org.apache.tools.ant.util.Retryable;
+import org.apache.tools.ant.util.VectorSet;
+
+/**
+ * Basic FTP client. Performs the following actions:
+ * <ul>
+ * <li> <strong>send</strong> - send files to a remote server. This is the
+ * default action.</li>
+ * <li> <strong>get</strong> - retrieve files from a remote server.</li>
+ * <li> <strong>del</strong> - delete files from a remote server.</li>
+ * <li> <strong>list</strong> - create a file listing.</li>
+ * <li> <strong>chmod</strong> - change unix file permissions.</li>
+ * <li> <strong>rmdir</strong> - remove directories, if empty, from a
+ * remote server.</li>
+ * </ul>
+ * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
+ * to hold data ports open after a "retr" operation, allowing them to timeout
+ * instead of shutting them down cleanly. This happens in active or passive
+ * mode, and the ports will remain open even after ending the FTP session. FTP
+ * "send" operations seem to close ports immediately. This behavior may cause
+ * problems on some systems when downloading large sets of files.
+ *
+ * @since Ant 1.3
+ */
+public class FTP extends Task implements FTPTaskConfig {
+ protected static final int SEND_FILES = 0;
+ protected static final int GET_FILES = 1;
+ protected static final int DEL_FILES = 2;
+ protected static final int LIST_FILES = 3;
+ protected static final int MK_DIR = 4;
+ protected static final int CHMOD = 5;
+ protected static final int RM_DIR = 6;
+ protected static final int SITE_CMD = 7;
+ /** return code of ftp */
+ private static final int CODE_521 = 521;
+ private static final int CODE_550 = 550;
+ private static final int CODE_553 = 553;
+
+ /** adjust uptodate calculations where server timestamps are HH:mm and client's
+ * are HH:mm:ss */
+ private static final long GRANULARITY_MINUTE = 60000L;
+
+ /** Date formatter used in logging, note not thread safe! */
+ private static final SimpleDateFormat TIMESTAMP_LOGGING_SDF =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ /** Default port for FTP */
+ public static final int DEFAULT_FTP_PORT = 21;
+
+ private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
+
+ private String remotedir;
+ private String server;
+ private String userid;
+ private String password;
+ private String account;
+ private File listing;
+ private boolean binary = true;
+ private boolean passive = false;
+ private boolean verbose = false;
+ private boolean newerOnly = false;
+ private long timeDiffMillis = 0;
+ private long granularityMillis = 0L;
+ private boolean timeDiffAuto = false;
+ private int action = SEND_FILES;
+ private Vector filesets = new Vector();
+ private Set dirCache = new HashSet();
+ private int transferred = 0;
+ private String remoteFileSep = "/";
+ private int port = DEFAULT_FTP_PORT;
+ private boolean skipFailedTransfers = false;
+ private int skipped = 0;
+ private boolean ignoreNoncriticalErrors = false;
+ private boolean preserveLastModified = false;
+ private String chmod = null;
+ private String umask = null;
+ private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
+ private String defaultDateFormatConfig = null;
+ private String recentDateFormatConfig = null;
+ private LanguageCode serverLanguageCodeConfig = LanguageCode.getDefault();
+ private String serverTimeZoneConfig = null;
+ private String shortMonthNamesConfig = null;
+ private Granularity timestampGranularity = Granularity.getDefault();
+ private boolean isConfigurationSet = false;
+ private int retriesAllowed = 0;
+ private String siteCommand = null;
+ private String initialSiteCommand = null;
+ private boolean enableRemoteVerification = true;
+
+ protected static final String[] ACTION_STRS = {
+ "sending",
+ "getting",
+ "deleting",
+ "listing",
+ "making directory",
+ "chmod",
+ "removing",
+ "site"
+ };
+
+ protected static final String[] COMPLETED_ACTION_STRS = {
+ "sent",
+ "retrieved",
+ "deleted",
+ "listed",
+ "created directory",
+ "mode changed",
+ "removed",
+ "site command executed"
+ };
+
+ protected static final String[] ACTION_TARGET_STRS = {
+ "files",
+ "files",
+ "files",
+ "files",
+ "directory",
+ "files",
+ "directories",
+ "site command"
+ };
+
+ /**
+ * internal class providing a File-like interface to some of the information
+ * available from the FTP server
+ *
+ */
+ protected static class FTPFileProxy extends File {
+
+ private final FTPFile file;
+ private final String[] parts;
+ private final String name;
+
+ /**
+ * creates a proxy to a FTP file
+ * @param file
+ */
+ public FTPFileProxy(FTPFile file) {
+ super(file.getName());
+ name = file.getName();
+ this.file = file;
+ parts = FileUtils.getPathStack(name);
+ }
+
+ /**
+ * creates a proxy to a FTP directory
+ * @param completePath the remote directory.
+ */
+ public FTPFileProxy(String completePath) {
+ super(completePath);
+ file = null;
+ name = completePath;
+ parts = FileUtils.getPathStack(completePath);
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#exists()
+ */
+ public boolean exists() {
+ return true;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#getAbsolutePath()
+ */
+ public String getAbsolutePath() {
+ return name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#getName()
+ */
+ public String getName() {
+ return parts.length > 0 ? parts[parts.length - 1] : name;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#getParent()
+ */
+ public String getParent() {
+ String result = "";
+ for(int i = 0; i < parts.length - 1; i++){
+ result += File.separatorChar + parts[i];
+ }
+ return result;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#getPath()
+ */
+ public String getPath() {
+ return name;
+ }
+
+
+ /**
+ * FTP files are stored as absolute paths
+ * @return true
+ */
+ public boolean isAbsolute() {
+ return true;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#isDirectory()
+ */
+ public boolean isDirectory() {
+ return file == null;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#isFile()
+ */
+ public boolean isFile() {
+ return file != null;
+ }
+
+
+ /**
+ * FTP files cannot be hidden
+ *
+ * @return false
+ */
+ public boolean isHidden() {
+ return false;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#lastModified()
+ */
+ public long lastModified() {
+ if (file != null) {
+ return file.getTimestamp().getTimeInMillis();
+ }
+ return 0;
+ }
+
+
+ /* (non-Javadoc)
+ * @see java.io.File#length()
+ */
+ public long length() {
+ if (file != null) {
+ return file.getSize();
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * internal class allowing to read the contents of a remote file system
+ * using the FTP protocol
+ * used in particular for ftp get operations
+ * differences with DirectoryScanner
+ * "" (the root of the fileset) is never included in the included directories
+ * followSymlinks defaults to false
+ */
+ protected class FTPDirectoryScanner extends DirectoryScanner {
+ // CheckStyle:VisibilityModifier OFF - bc
+ protected FTPClient ftp = null;
+ // CheckStyle:VisibilityModifier ON
+
+ private String rootPath = null;
+
+ /**
+ * since ant 1.6
+ * this flag should be set to true on UNIX and can save scanning time
+ */
+ private boolean remoteSystemCaseSensitive = false;
+ private boolean remoteSensitivityChecked = false;
+
+ /**
+ * constructor
+ * @param ftp ftpclient object
+ */
+ public FTPDirectoryScanner(FTPClient ftp) {
+ super();
+ this.ftp = ftp;
+ this.setFollowSymlinks(false);
+ }
+
+
+ /**
+ * scans the remote directory,
+ * storing internally the included files, directories, ...
+ */
+ public void scan() {
+ if (includes == null) {
+ // No includes supplied, so set it to 'matches all'
+ includes = new String[1];
+ includes[0] = "**";
+ }
+ if (excludes == null) {
+ excludes = new String[0];
+ }
+
+ filesIncluded = new VectorSet();
+ filesNotIncluded = new Vector();
+ filesExcluded = new VectorSet();
+ dirsIncluded = new VectorSet();
+ dirsNotIncluded = new Vector();
+ dirsExcluded = new VectorSet();
+
+ try {
+ String cwd = ftp.printWorkingDirectory();
+ // always start from the current ftp working dir
+ forceRemoteSensitivityCheck();
+
+ checkIncludePatterns();
+ clearCaches();
+ ftp.changeWorkingDirectory(cwd);
+ } catch (IOException e) {
+ throw new BuildException("Unable to scan FTP server: ", e);
+ }
+ }
+
+
+ /**
+ * this routine is actually checking all the include patterns in
+ * order to avoid scanning everything under base dir
+ * @since ant1.6
+ */
+ private void checkIncludePatterns() {
+
+ Hashtable newroots = new Hashtable();
+ // put in the newroots vector the include patterns without
+ // wildcard tokens
+ for (int icounter = 0; icounter < includes.length; icounter++) {
+ String newpattern =
+ SelectorUtils.rtrimWildcardTokens(includes[icounter]);
+ newroots.put(newpattern, includes[icounter]);
+ }
+ if (remotedir == null) {
+ try {
+ remotedir = ftp.printWorkingDirectory();
+ } catch (IOException e) {
+ throw new BuildException("could not read current ftp directory",
+ getLocation());
+ }
+ }
+ AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
+ rootPath = baseFTPFile.getAbsolutePath();
+ // construct it
+ if (newroots.containsKey("")) {
+ // we are going to scan everything anyway
+ scandir(rootPath, "", true);
+ } else {
+ // only scan directories that can include matched files or
+ // directories
+ Enumeration enum2 = newroots.keys();
+
+ while (enum2.hasMoreElements()) {
+ String currentelement = (String) enum2.nextElement();
+ String originalpattern = (String) newroots.get(currentelement);
+ AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement);
+ boolean isOK = true;
+ boolean traversesSymlinks = false;
+ String path = null;
+
+ if (myfile.exists()) {
+ forceRemoteSensitivityCheck();
+ if (remoteSensitivityChecked
+ && remoteSystemCaseSensitive && isFollowSymlinks()) {
+ // cool case,
+ //we do not need to scan all the subdirs in the relative path
+ path = myfile.getFastRelativePath();
+ } else {
+ // may be on a case insensitive file system. We want
+ // the results to show what's really on the disk, so
+ // we need to double check.
+ try {
+ path = myfile.getRelativePath();
+ traversesSymlinks = myfile.isTraverseSymlinks();
+ } catch (IOException be) {
+ throw new BuildException(be, getLocation());
+ } catch (BuildException be) {
+ isOK = false;
+ }
+ }
+ } else {
+ isOK = false;
+ }
+ if (isOK) {
+ currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar);
+ if (!isFollowSymlinks()
+ && traversesSymlinks) {
+ continue;
+ }
+
+ if (myfile.isDirectory()) {
+ if (isIncluded(currentelement)
+ && currentelement.length() > 0) {
+ accountForIncludedDir(currentelement, myfile, true);
+ } else {
+ if (currentelement.length() > 0) {
+ if (currentelement.charAt(currentelement
+ .length() - 1)
+ != File.separatorChar) {
+ currentelement =
+ currentelement + File.separatorChar;
+ }
+ }
+ scandir(myfile.getAbsolutePath(), currentelement, true);
+ }
+ } else {
+ if (isCaseSensitive
+ && originalpattern.equals(currentelement)) {
+ accountForIncludedFile(currentelement);
+ } else if (!isCaseSensitive
+ && originalpattern
+ .equalsIgnoreCase(currentelement)) {
+ accountForIncludedFile(currentelement);
+ }
+ }
+ }
+ }
+ }
+ }
+ /**
+ * scans a particular directory. populates the scannedDirs cache.
+ *
+ * @param dir directory to scan
+ * @param vpath relative path to the base directory of the remote fileset
+ * always ended with a File.separator
+ * @param fast seems to be always true in practice
+ */
+ protected void scandir(String dir, String vpath, boolean fast) {
+ // avoid double scanning of directories, can only happen in fast mode
+ if (fast && hasBeenScanned(vpath)) {
+ return;
+ }
+ try {
+ if (!ftp.changeWorkingDirectory(dir)) {
+ return;
+ }
+ String completePath = null;
+ if (!vpath.equals("")) {
+ completePath = rootPath + remoteFileSep
+ + vpath.replace(File.separatorChar, remoteFileSep.charAt(0));
+ } else {
+ completePath = rootPath;
+ }
+ FTPFile[] newfiles = listFiles(completePath, false);
+
+ if (newfiles == null) {
+ ftp.changeToParentDirectory();
+ return;
+ }
+ for (int i = 0; i < newfiles.length; i++) {
+ FTPFile file = newfiles[i];
+ if (file != null
+ && !file.getName().equals(".")
+ && !file.getName().equals("..")) {
+ String name = vpath + file.getName();
+ scannedDirs.put(name, new FTPFileProxy(file));
+ if (isFunctioningAsDirectory(ftp, dir, file)) {
+ boolean slowScanAllowed = true;
+ if (!isFollowSymlinks() && file.isSymbolicLink()) {
+ dirsExcluded.addElement(name);
+ slowScanAllowed = false;
+ } else if (isIncluded(name)) {
+ accountForIncludedDir(name,
+ new AntFTPFile(ftp, file, completePath) , fast);
+ } else {
+ dirsNotIncluded.addElement(name);
+ if (fast && couldHoldIncluded(name)) {
+ scandir(file.getName(),
+ name + File.separator, fast);
+ }
+ }
+ if (!fast && slowScanAllowed) {
+ scandir(file.getName(),
+ name + File.separator, fast);
+ }
+ } else {
+ if (!isFollowSymlinks() && file.isSymbolicLink()) {
+ filesExcluded.addElement(name);
+ } else if (isFunctioningAsFile(ftp, dir, file)) {
+ accountForIncludedFile(name);
+ }
+ }
+ }
+ }
+ ftp.changeToParentDirectory();
+ } catch (IOException e) {
+ throw new BuildException("Error while communicating with FTP "
+ + "server: ", e);
+ }
+ }
+ /**
+ * process included file
+ * @param name path of the file relative to the directory of the fileset
+ */
+ private void accountForIncludedFile(String name) {
+ if (!filesIncluded.contains(name)
+ && !filesExcluded.contains(name)) {
+
+ if (isIncluded(name)) {
+ if (!isExcluded(name)
+ && isSelected(name, (File) scannedDirs.get(name))) {
+ filesIncluded.addElement(name);
+ } else {
+ filesExcluded.addElement(name);
+ }
+ } else {
+ filesNotIncluded.addElement(name);
+ }
+ }
+ }
+
+ /**
+ *
+ * @param name path of the directory relative to the directory of
+ * the fileset
+ * @param file directory as file
+ * @param fast
+ */
+ private void accountForIncludedDir(String name, AntFTPFile file, boolean fast) {
+ if (!dirsIncluded.contains(name)
+ && !dirsExcluded.contains(name)) {
+
+ if (!isExcluded(name)) {
+ if (fast) {
+ if (file.isSymbolicLink()) {
+ try {
+ file.getClient().changeWorkingDirectory(file.curpwd);
+ } catch (IOException ioe) {
+ throw new BuildException("could not change directory to curpwd");
+ }
+ scandir(file.getLink(),
+ name + File.separator, fast);
+ } else {
+ try {
+ file.getClient().changeWorkingDirectory(file.curpwd);
+ } catch (IOException ioe) {
+ throw new BuildException("could not change directory to curpwd");
+ }
+ scandir(file.getName(),
+ name + File.separator, fast);
+ }
+ }
+ dirsIncluded.addElement(name);
+ } else {
+ dirsExcluded.addElement(name);
+ if (fast && couldHoldIncluded(name)) {
+ try {
+ file.getClient().changeWorkingDirectory(file.curpwd);
+ } catch (IOException ioe) {
+ throw new BuildException("could not change directory to curpwd");
+ }
+ scandir(file.getName(),
+ name + File.separator, fast);
+ }
+ }
+ }
+ }
+ /**
+ * temporary table to speed up the various scanning methods below
+ *
+ * @since Ant 1.6
+ */
+ private Map fileListMap = new HashMap();
+ /**
+ * List of all scanned directories.
+ *
+ * @since Ant 1.6
+ */
+
+ private Map scannedDirs = new HashMap();
+
+ /**
+ * Has the directory with the given path relative to the base
+ * directory already been scanned?
+ *
+ * @since Ant 1.6
+ */
+ private boolean hasBeenScanned(String vpath) {
+ return scannedDirs.containsKey(vpath);
+ }
+
+ /**
+ * Clear internal caches.
+ *
+ * @since Ant 1.6
+ */
+ private void clearCaches() {
+ fileListMap.clear();
+ scannedDirs.clear();
+ }
+ /**
+ * list the files present in one directory.
+ * @param directory full path on the remote side
+ * @param changedir if true change to directory directory before listing
+ * @return array of FTPFile
+ */
+ public FTPFile[] listFiles(String directory, boolean changedir) {
+ //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
+ String currentPath = directory;
+ if (changedir) {
+ try {
+ boolean result = ftp.changeWorkingDirectory(directory);
+ if (!result) {
+ return null;
+ }
+ currentPath = ftp.printWorkingDirectory();
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ }
+ if (fileListMap.containsKey(currentPath)) {
+ getProject().log("filelist map used in listing files", Project.MSG_DEBUG);
+ return ((FTPFile[]) fileListMap.get(currentPath));
+ }
+ FTPFile[] result = null;
+ try {
+ result = ftp.listFiles();
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ fileListMap.put(currentPath, result);
+ if (!remoteSensitivityChecked) {
+ checkRemoteSensitivity(result, directory);
+ }
+ return result;
+ }
+
+ private void forceRemoteSensitivityCheck() {
+ if (!remoteSensitivityChecked) {
+ try {
+ checkRemoteSensitivity(ftp.listFiles(), ftp.printWorkingDirectory());
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ }
+ }
+ /**
+ * cd into one directory and
+ * list the files present in one directory.
+ * @param directory full path on the remote side
+ * @return array of FTPFile
+ */
+ public FTPFile[] listFiles(String directory) {
+ return listFiles(directory, true);
+ }
+ private void checkRemoteSensitivity(FTPFile[] array, String directory) {
+ if (array == null) {
+ return;
+ }
+ boolean candidateFound = false;
+ String target = null;
+ for (int icounter = 0; icounter < array.length; icounter++) {
+ if (array[icounter] != null && array[icounter].isDirectory()) {
+ if (!array[icounter].getName().equals(".")
+ && !array[icounter].getName().equals("..")) {
+ candidateFound = true;
+ target = fiddleName(array[icounter].getName());
+ getProject().log("will try to cd to "
+ + target + " where a directory called " + array[icounter].getName()
+ + " exists", Project.MSG_DEBUG);
+ for (int pcounter = 0; pcounter < array.length; pcounter++) {
+ if (array[pcounter] != null
+ && pcounter != icounter
+ && target.equals(array[pcounter].getName())) {
+ candidateFound = false;
+ break;
+ }
+ }
+ if (candidateFound) {
+ break;
+ }
+ }
+ }
+ }
+ if (candidateFound) {
+ try {
+ getProject().log("testing case sensitivity, attempting to cd to "
+ + target, Project.MSG_DEBUG);
+ remoteSystemCaseSensitive = !ftp.changeWorkingDirectory(target);
+ } catch (IOException ioe) {
+ remoteSystemCaseSensitive = true;
+ } finally {
+ try {
+ ftp.changeWorkingDirectory(directory);
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ }
+ getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive,
+ Project.MSG_VERBOSE);
+ remoteSensitivityChecked = true;
+ }
+ }
+ private String fiddleName(String origin) {
+ StringBuffer result = new StringBuffer();
+ for (int icounter = 0; icounter < origin.length(); icounter++) {
+ if (Character.isLowerCase(origin.charAt(icounter))) {
+ result.append(Character.toUpperCase(origin.charAt(icounter)));
+ } else if (Character.isUpperCase(origin.charAt(icounter))) {
+ result.append(Character.toLowerCase(origin.charAt(icounter)));
+ } else {
+ result.append(origin.charAt(icounter));
+ }
+ }
+ return result.toString();
+ }
+ /**
+ * an AntFTPFile is a representation of a remote file
+ * @since Ant 1.6
+ */
+ protected class AntFTPFile {
+ /**
+ * ftp client
+ */
+ private FTPClient client;
+ /**
+ * parent directory of the file
+ */
+ private String curpwd;
+ /**
+ * the file itself
+ */
+ private FTPFile ftpFile;
+ /**
+ *
+ */
+ private AntFTPFile parent = null;
+ private boolean relativePathCalculated = false;
+ private boolean traversesSymlinks = false;
+ private String relativePath = "";
+ /**
+ * constructor
+ * @param client ftp client variable
+ * @param ftpFile the file
+ * @param curpwd absolute remote path where the file is found
+ */
+ public AntFTPFile(FTPClient client, FTPFile ftpFile, String curpwd) {
+ this.client = client;
+ this.ftpFile = ftpFile;
+ this.curpwd = curpwd;
+ }
+ /**
+ * other constructor
+ * @param parent the parent file
+ * @param path a relative path to the parent file
+ */
+ public AntFTPFile(AntFTPFile parent, String path) {
+ this.parent = parent;
+ this.client = parent.client;
+ Vector pathElements = SelectorUtils.tokenizePath(path);
+ try {
+ boolean result = this.client.changeWorkingDirectory(parent.getAbsolutePath());
+ //this should not happen, except if parent has been deleted by another process
+ if (!result) {
+ return;
+ }
+ this.curpwd = parent.getAbsolutePath();
+ } catch (IOException ioe) {
+ throw new BuildException("could not change working dir to "
+ + parent.curpwd);
+ }
+ final int size = pathElements.size();
+ for (int fcount = 0; fcount < size - 1; fcount++) {
+ String currentPathElement = (String) pathElements.elementAt(fcount);
+ try {
+ boolean result = this.client.changeWorkingDirectory(currentPathElement);
+ if (!result && !isCaseSensitive()
+ && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
+ currentPathElement = findPathElementCaseUnsensitive(this.curpwd,
+ currentPathElement);
+ if (currentPathElement == null) {
+ return;
+ }
+ } else if (!result) {
+ return;
+ }
+ this.curpwd = getCurpwdPlusFileSep()
+ + currentPathElement;
+ } catch (IOException ioe) {
+ throw new BuildException("could not change working dir to "
+ + (String) pathElements.elementAt(fcount)
+ + " from " + this.curpwd);
+ }
+
+ }
+ String lastpathelement = (String) pathElements.elementAt(size - 1);
+ FTPFile [] theFiles = listFiles(this.curpwd);
+ this.ftpFile = getFile(theFiles, lastpathelement);
+ }
+ /**
+ * find a file in a directory in case unsensitive way
+ * @param parentPath where we are
+ * @param soughtPathElement what is being sought
+ * @return the first file found or null if not found
+ */
+ private String findPathElementCaseUnsensitive(String parentPath,
+ String soughtPathElement) {
+ // we are already in the right path, so the second parameter
+ // is false
+ FTPFile[] theFiles = listFiles(parentPath, false);
+ if (theFiles == null) {
+ return null;
+ }
+ for (int icounter = 0; icounter < theFiles.length; icounter++) {
+ if (theFiles[icounter] != null
+ && theFiles[icounter].getName().equalsIgnoreCase(soughtPathElement)) {
+ return theFiles[icounter].getName();
+ }
+ }
+ return null;
+ }
+ /**
+ * find out if the file exists
+ * @return true if the file exists
+ */
+ public boolean exists() {
+ return (ftpFile != null);
+ }
+ /**
+ * if the file is a symbolic link, find out to what it is pointing
+ * @return the target of the symbolic link
+ */
+ public String getLink() {
+ return ftpFile.getLink();
+ }
+ /**
+ * get the name of the file
+ * @return the name of the file
+ */
+ public String getName() {
+ return ftpFile.getName();
+ }
+ /**
+ * find out the absolute path of the file
+ * @return absolute path as string
+ */
+ public String getAbsolutePath() {
+ return getCurpwdPlusFileSep() + ftpFile.getName();
+ }
+ /**
+ * find out the relative path assuming that the path used to construct
+ * this AntFTPFile was spelled properly with regards to case.
+ * This is OK on a case sensitive system such as UNIX
+ * @return relative path
+ */
+ public String getFastRelativePath() {
+ String absPath = getAbsolutePath();
+ if (absPath.startsWith(rootPath + remoteFileSep)) {
+ return absPath.substring(rootPath.length() + remoteFileSep.length());
+ }
+ return null;
+ }
+ /**
+ * find out the relative path to the rootPath of the enclosing scanner.
+ * this relative path is spelled exactly like on disk,
+ * for instance if the AntFTPFile has been instantiated as ALPHA,
+ * but the file is really called alpha, this method will return alpha.
+ * If a symbolic link is encountered, it is followed, but the name of the link
+ * rather than the name of the target is returned.
+ * (ie does not behave like File.getCanonicalPath())
+ * @return relative path, separated by remoteFileSep
+ * @throws IOException if a change directory fails, ...
+ * @throws BuildException if one of the components of the relative path cannot
+ * be found.
+ */
+ public String getRelativePath() throws IOException, BuildException {
+ if (!relativePathCalculated) {
+ if (parent != null) {
+ traversesSymlinks = parent.isTraverseSymlinks();
+ relativePath = getRelativePath(parent.getAbsolutePath(),
+ parent.getRelativePath());
+ } else {
+ relativePath = getRelativePath(rootPath, "");
+ relativePathCalculated = true;
+ }
+ }
+ return relativePath;
+ }
+ /**
+ * get the relative path of this file
+ * @param currentPath base path
+ * @param currentRelativePath relative path of the base path with regards to remote dir
+ * @return relative path
+ */
+ private String getRelativePath(String currentPath, String currentRelativePath) {
+ Vector pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep);
+ Vector pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep);
+ String relPath = currentRelativePath;
+ final int size = pathElements.size();
+ for (int pcount = pathElements2.size(); pcount < size; pcount++) {
+ String currentElement = (String) pathElements.elementAt(pcount);
+ FTPFile[] theFiles = listFiles(currentPath);
+ FTPFile theFile = null;
+ if (theFiles != null) {
+ theFile = getFile(theFiles, currentElement);
+ }
+ if (!relPath.equals("")) {
+ relPath = relPath + remoteFileSep;
+ }
+ if (theFile == null) {
+ // hit a hidden file assume not a symlink
+ relPath = relPath + currentElement;
+ currentPath = currentPath + remoteFileSep + currentElement;
+ log("Hidden file " + relPath
+ + " assumed to not be a symlink.",
+ Project.MSG_VERBOSE);
+ } else {
+ traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink();
+ relPath = relPath + theFile.getName();
+ currentPath = currentPath + remoteFileSep + theFile.getName();
+ }
+ }
+ return relPath;
+ }
+ /**
+ * find a file matching a string in an array of FTPFile.
+ * This method will find "alpha" when requested for "ALPHA"
+ * if and only if the caseSensitive attribute is set to false.
+ * When caseSensitive is set to true, only the exact match is returned.
+ * @param theFiles array of files
+ * @param lastpathelement the file name being sought
+ * @return null if the file cannot be found, otherwise return the matching file.
+ */
+ public FTPFile getFile(FTPFile[] theFiles, String lastpathelement) {
+ if (theFiles == null) {
+ return null;
+ }
+ for (int fcount = 0; fcount < theFiles.length; fcount++) {
+ if (theFiles[fcount] != null) {
+ if (theFiles[fcount].getName().equals(lastpathelement)) {
+ return theFiles[fcount];
+ } else if (!isCaseSensitive()
+ && theFiles[fcount].getName().equalsIgnoreCase(
+ lastpathelement)) {
+ return theFiles[fcount];
+ }
+ }
+ }
+ return null;
+ }
+ /**
+ * tell if a file is a directory.
+ * note that it will return false for symbolic links pointing to directories.
+ * @return <code>true</code> for directories
+ */
+ public boolean isDirectory() {
+ return ftpFile.isDirectory();
+ }
+ /**
+ * tell if a file is a symbolic link
+ * @return <code>true</code> for symbolic links
+ */
+ public boolean isSymbolicLink() {
+ return ftpFile.isSymbolicLink();
+ }
+ /**
+ * return the attached FTP client object.
+ * Warning : this instance is really shared with the enclosing class.
+ * @return FTP client
+ */
+ protected FTPClient getClient() {
+ return client;
+ }
+
+ /**
+ * sets the current path of an AntFTPFile
+ * @param curpwd the current path one wants to set
+ */
+ protected void setCurpwd(String curpwd) {
+ this.curpwd = curpwd;
+ }
+ /**
+ * returns the path of the directory containing the AntFTPFile.
+ * of the full path of the file itself in case of AntFTPRootFile
+ * @return parent directory of the AntFTPFile
+ */
+ public String getCurpwd() {
+ return curpwd;
+ }
+ /**
+ * returns the path of the directory containing the AntFTPFile.
+ * of the full path of the file itself in case of AntFTPRootFile
+ * and appends the remote file separator if necessary.
+ * @return parent directory of the AntFTPFile
+ * @since Ant 1.8.2
+ */
+ public String getCurpwdPlusFileSep() {
+ return curpwd.endsWith(remoteFileSep) ? curpwd
+ : curpwd + remoteFileSep;
+ }
+ /**
+ * find out if a symbolic link is encountered in the relative path of this file
+ * from rootPath.
+ * @return <code>true</code> if a symbolic link is encountered in the relative path.
+ * @throws IOException if one of the change directory or directory listing operations
+ * fails
+ * @throws BuildException if a path component in the relative path cannot be found.
+ */
+ public boolean isTraverseSymlinks() throws IOException, BuildException {
+ if (!relativePathCalculated) {
+ // getRelativePath also finds about symlinks
+ getRelativePath();
+ }
+ return traversesSymlinks;
+ }
+
+ /**
+ * Get a string rep of this object.
+ * @return a string containing the pwd and the file.
+ */
+ public String toString() {
+ return "AntFtpFile: " + curpwd + "%" + ftpFile;
+ }
+ }
+ /**
+ * special class to represent the remote directory itself
+ * @since Ant 1.6
+ */
+ protected class AntFTPRootFile extends AntFTPFile {
+ private String remotedir;
+ /**
+ * constructor
+ * @param aclient FTP client
+ * @param remotedir remote directory
+ */
+ public AntFTPRootFile(FTPClient aclient, String remotedir) {
+ super(aclient, null, remotedir);
+ this.remotedir = remotedir;
+ try {
+ this.getClient().changeWorkingDirectory(this.remotedir);
+ this.setCurpwd(this.getClient().printWorkingDirectory());
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ }
+ /**
+ * find the absolute path
+ * @return absolute path
+ */
+ public String getAbsolutePath() {
+ return this.getCurpwd();
+ }
+ /**
+ * find out the relative path to root
+ * @return empty string
+ * @throws BuildException actually never
+ * @throws IOException actually never
+ */
+ public String getRelativePath() throws BuildException, IOException {
+ return "";
+ }
+ }
+ }
+ /**
+ * check FTPFiles to check whether they function as directories too
+ * the FTPFile API seem to make directory and symbolic links incompatible
+ * we want to find out if we can cd to a symbolic link
+ * @param dir the parent directory of the file to test
+ * @param file the file to test
+ * @return true if it is possible to cd to this directory
+ * @since ant 1.6
+ */
+ private boolean isFunctioningAsDirectory(FTPClient ftp, String dir, FTPFile file) {
+ boolean result = false;
+ String currentWorkingDir = null;
+ if (file.isDirectory()) {
+ return true;
+ } else if (file.isFile()) {
+ return false;
+ }
+ try {
+ currentWorkingDir = ftp.printWorkingDirectory();
+ } catch (IOException ioe) {
+ getProject().log("could not find current working directory " + dir
+ + " while checking a symlink",
+ Project.MSG_DEBUG);
+ }
+ if (currentWorkingDir != null) {
+ try {
+ result = ftp.changeWorkingDirectory(file.getLink());
+ } catch (IOException ioe) {
+ getProject().log("could not cd to " + file.getLink() + " while checking a symlink",
+ Project.MSG_DEBUG);
+ }
+ if (result) {
+ boolean comeback = false;
+ try {
+ comeback = ftp.changeWorkingDirectory(currentWorkingDir);
+ } catch (IOException ioe) {
+ getProject().log("could not cd back to " + dir + " while checking a symlink",
+ Project.MSG_ERR);
+ } finally {
+ if (!comeback) {
+ throw new BuildException("could not cd back to " + dir
+ + " while checking a symlink");
+ }
+ }
+ }
+ }
+ return result;
+ }
+ /**
+ * check FTPFiles to check whether they function as directories too
+ * the FTPFile API seem to make directory and symbolic links incompatible
+ * we want to find out if we can cd to a symbolic link
+ * @param dir the parent directory of the file to test
+ * @param file the file to test
+ * @return true if it is possible to cd to this directory
+ * @since ant 1.6
+ */
+ private boolean isFunctioningAsFile(FTPClient ftp, String dir, FTPFile file) {
+ if (file.isDirectory()) {
+ return false;
+ } else if (file.isFile()) {
+ return true;
+ }
+ return !isFunctioningAsDirectory(ftp, dir, file);
+ }
+ /**
+ * Sets the remote directory where files will be placed. This may be a
+ * relative or absolute path, and must be in the path syntax expected by
+ * the remote server. No correction of path syntax will be performed.
+ *
+ * @param dir the remote directory name.
+ */
+ public void setRemotedir(String dir) {
+ this.remotedir = dir;
+ }
+
+
+ /**
+ * Sets the FTP server to send files to.
+ *
+ * @param server the remote server name.
+ */
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+
+ /**
+ * Sets the FTP port used by the remote server.
+ *
+ * @param port the port on which the remote server is listening.
+ */
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+
+ /**
+ * Sets the login user id to use on the specified server.
+ *
+ * @param userid remote system userid.
+ */
+ public void setUserid(String userid) {
+ this.userid = userid;
+ }
+
+
+ /**
+ * Sets the login password for the given user id.
+ *
+ * @param password the password on the remote system.
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * Sets the login account to use on the specified server.
+ *
+ * @param pAccount the account name on remote system
+ * @since Ant 1.7
+ */
+ public void setAccount(String pAccount) {
+ this.account = pAccount;
+ }
+
+
+ /**
+ * If true, uses binary mode, otherwise text mode (default is binary).
+ *
+ * @param binary if true use binary mode in transfers.
+ */
+ public void setBinary(boolean binary) {
+ this.binary = binary;
+ }
+
+
+ /**
+ * Specifies whether to use passive mode. Set to true if you are behind a
+ * firewall and cannot connect without it. Passive mode is disabled by
+ * default.
+ *
+ * @param passive true is passive mode should be used.
+ */
+ public void setPassive(boolean passive) {
+ this.passive = passive;
+ }
+
+
+ /**
+ * Set to true to receive notification about each file as it is
+ * transferred.
+ *
+ * @param verbose true if verbose notifications are required.
+ */
+ public void setVerbose(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+
+ /**
+ * A synonym for <tt>depends</tt>. Set to true to transmit only new
+ * or changed files.
+ *
+ * See the related attributes timediffmillis and timediffauto.
+ *
+ * @param newer if true only transfer newer files.
+ */
+ public void setNewer(boolean newer) {
+ this.newerOnly = newer;
+ }
+
+ /**
+ * number of milliseconds to add to the time on the remote machine
+ * to get the time on the local machine.
+ *
+ * use in conjunction with <code>newer</code>
+ *
+ * @param timeDiffMillis number of milliseconds
+ *
+ * @since ant 1.6
+ */
+ public void setTimeDiffMillis(long timeDiffMillis) {
+ this.timeDiffMillis = timeDiffMillis;
+ }
+
+ /**
+ * &quot;true&quot; to find out automatically the time difference
+ * between local and remote machine.
+ *
+ * This requires right to create
+ * and delete a temporary file in the remote directory.
+ *
+ * @param timeDiffAuto true = find automatically the time diff
+ *
+ * @since ant 1.6
+ */
+ public void setTimeDiffAuto(boolean timeDiffAuto) {
+ this.timeDiffAuto = timeDiffAuto;
+ }
+
+ /**
+ * Set to true to preserve modification times for "gotten" files.
+ *
+ * @param preserveLastModified if true preserver modification times.
+ */
+ public void setPreserveLastModified(boolean preserveLastModified) {
+ this.preserveLastModified = preserveLastModified;
+ }
+
+
+ /**
+ * Set to true to transmit only files that are new or changed from their
+ * remote counterparts. The default is to transmit all files.
+ *
+ * @param depends if true only transfer newer files.
+ */
+ public void setDepends(boolean depends) {
+ this.newerOnly = depends;
+ }
+
+
+ /**
+ * Sets the remote file separator character. This normally defaults to the
+ * Unix standard forward slash, but can be manually overridden using this
+ * call if the remote server requires some other separator. Only the first
+ * character of the string is used.
+ *
+ * @param separator the file separator on the remote system.
+ */
+ public void setSeparator(String separator) {
+ remoteFileSep = separator;
+ }
+
+
+ /**
+ * Sets the file permission mode (Unix only) for files sent to the
+ * server.
+ *
+ * @param theMode unix style file mode for the files sent to the remote
+ * system.
+ */
+ public void setChmod(String theMode) {
+ this.chmod = theMode;
+ }
+
+
+ /**
+ * Sets the default mask for file creation on a unix server.
+ *
+ * @param theUmask unix style umask for files created on the remote server.
+ */
+ public void setUmask(String theUmask) {
+ this.umask = theUmask;
+ }
+
+
+ /**
+ * A set of files to upload or download
+ *
+ * @param set the set of files to be added to the list of files to be
+ * transferred.
+ */
+ public void addFileset(FileSet set) {
+ filesets.addElement(set);
+ }
+
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir", "chmod", "list", and "site".
+ *
+ * @deprecated since 1.5.x.
+ * setAction(String) is deprecated and is replaced with
+ * setAction(FTP.Action) to make Ant's Introspection mechanism do the
+ * work and also to encapsulate operations on the type in its own
+ * class.
+ * @ant.attribute ignore="true"
+ *
+ * @param action the FTP action to be performed.
+ *
+ * @throws BuildException if the action is not a valid action.
+ */
+ public void setAction(String action) throws BuildException {
+ log("DEPRECATED - The setAction(String) method has been deprecated."
+ + " Use setAction(FTP.Action) instead.");
+
+ Action a = new Action();
+
+ a.setValue(action);
+ this.action = a.getAction();
+ }
+
+
+ /**
+ * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
+ * "mkdir", "chmod", "list", and "site".
+ *
+ * @param action the FTP action to be performed.
+ *
+ * @throws BuildException if the action is not a valid action.
+ */
+ public void setAction(Action action) throws BuildException {
+ this.action = action.getAction();
+ }
+
+
+ /**
+ * The output file for the "list" action. This attribute is ignored for
+ * any other actions.
+ *
+ * @param listing file in which to store the listing.
+ */
+ public void setListing(File listing) {
+ this.listing = listing;
+ }
+
+
+ /**
+ * If true, enables unsuccessful file put, delete and get
+ * operations to be skipped with a warning and the remainder
+ * of the files still transferred.
+ *
+ * @param skipFailedTransfers true if failures in transfers are ignored.
+ */
+ public void setSkipFailedTransfers(boolean skipFailedTransfers) {
+ this.skipFailedTransfers = skipFailedTransfers;
+ }
+
+
+ /**
+ * set the flag to skip errors on directory creation.
+ * (and maybe later other server specific errors)
+ *
+ * @param ignoreNoncriticalErrors true if non-critical errors should not
+ * cause a failure.
+ */
+ public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
+ this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
+ }
+
+ private void configurationHasBeenSet() {
+ this.isConfigurationSet = true;
+ }
+
+ /**
+ * Sets the systemTypeKey attribute.
+ * Method for setting <code>FTPClientConfig</code> remote system key.
+ *
+ * @param systemKey the key to be set - BUT if blank
+ * the default value of null (which signifies "autodetect") will be kept.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setSystemTypeKey(FTPSystemType systemKey) {
+ if (systemKey != null && !systemKey.getValue().equals("")) {
+ this.systemTypeKey = systemKey;
+ configurationHasBeenSet();
+ }
+ }
+
+ /**
+ * Sets the defaultDateFormatConfig attribute.
+ * @param defaultDateFormat configuration to be set, unless it is
+ * null or empty string, in which case ignored.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setDefaultDateFormatConfig(String defaultDateFormat) {
+ if (defaultDateFormat != null && !defaultDateFormat.equals("")) {
+ this.defaultDateFormatConfig = defaultDateFormat;
+ configurationHasBeenSet();
+ }
+ }
+
+ /**
+ * Sets the recentDateFormatConfig attribute.
+ * @param recentDateFormat configuration to be set, unless it is
+ * null or empty string, in which case ignored.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setRecentDateFormatConfig(String recentDateFormat) {
+ if (recentDateFormat != null && !recentDateFormat.equals("")) {
+ this.recentDateFormatConfig = recentDateFormat;
+ configurationHasBeenSet();
+ }
+ }
+
+ /**
+ * Sets the serverLanguageCode attribute.
+ * @param serverLanguageCode configuration to be set, unless it is
+ * null or empty string, in which case ignored.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setServerLanguageCodeConfig(LanguageCode serverLanguageCode) {
+ if (serverLanguageCode != null && !"".equals(serverLanguageCode.getValue())) {
+ this.serverLanguageCodeConfig = serverLanguageCode;
+ configurationHasBeenSet();
+ }
+ }
+
+ /**
+ * Sets the serverTimeZoneConfig attribute.
+ * @param serverTimeZoneId configuration to be set, unless it is
+ * null or empty string, in which case ignored.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setServerTimeZoneConfig(String serverTimeZoneId) {
+ if (serverTimeZoneId != null && !serverTimeZoneId.equals("")) {
+ this.serverTimeZoneConfig = serverTimeZoneId;
+ configurationHasBeenSet();
+ }
+ }
+
+ /**
+ * Sets the shortMonthNamesConfig attribute
+ *
+ * @param shortMonthNames configuration to be set, unless it is
+ * null or empty string, in which case ignored.
+ * @see org.apache.commons.net.ftp.FTPClientConfig
+ */
+ public void setShortMonthNamesConfig(String shortMonthNames) {
+ if (shortMonthNames != null && !shortMonthNames.equals("")) {
+ this.shortMonthNamesConfig = shortMonthNames;
+ configurationHasBeenSet();
+ }
+ }
+
+
+
+ /**
+ * Defines how many times to retry executing FTP command before giving up.
+ * Default is 0 - try once and if failure then give up.
+ *
+ * @param retriesAllowed number of retries to allow. -1 means
+ * keep trying forever. "forever" may also be specified as a
+ * synonym for -1.
+ */
+ public void setRetriesAllowed(String retriesAllowed) {
+ if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
+ this.retriesAllowed = Retryable.RETRY_FOREVER;
+ } else {
+ try {
+ int retries = Integer.parseInt(retriesAllowed);
+ if (retries < Retryable.RETRY_FOREVER) {
+ throw new BuildException(
+ "Invalid value for retriesAllowed attribute: "
+ + retriesAllowed);
+
+ }
+ this.retriesAllowed = retries;
+ } catch (NumberFormatException px) {
+ throw new BuildException(
+ "Invalid value for retriesAllowed attribute: "
+ + retriesAllowed);
+
+ }
+
+ }
+ }
+ /**
+ * @return Returns the systemTypeKey.
+ */
+ public String getSystemTypeKey() {
+ return systemTypeKey.getValue();
+ }
+ /**
+ * @return Returns the defaultDateFormatConfig.
+ */
+ public String getDefaultDateFormatConfig() {
+ return defaultDateFormatConfig;
+ }
+ /**
+ * @return Returns the recentDateFormatConfig.
+ */
+ public String getRecentDateFormatConfig() {
+ return recentDateFormatConfig;
+ }
+ /**
+ * @return Returns the serverLanguageCodeConfig.
+ */
+ public String getServerLanguageCodeConfig() {
+ return serverLanguageCodeConfig.getValue();
+ }
+ /**
+ * @return Returns the serverTimeZoneConfig.
+ */
+ public String getServerTimeZoneConfig() {
+ return serverTimeZoneConfig;
+ }
+ /**
+ * @return Returns the shortMonthNamesConfig.
+ */
+ public String getShortMonthNamesConfig() {
+ return shortMonthNamesConfig;
+ }
+ /**
+ * @return Returns the timestampGranularity.
+ */
+ Granularity getTimestampGranularity() {
+ return timestampGranularity;
+ }
+ /**
+ * Sets the timestampGranularity attribute
+ * @param timestampGranularity The timestampGranularity to set.
+ */
+ public void setTimestampGranularity(Granularity timestampGranularity) {
+ if (null == timestampGranularity || "".equals(timestampGranularity.getValue())) {
+ return;
+ }
+ this.timestampGranularity = timestampGranularity;
+ }
+ /**
+ * Sets the siteCommand attribute. This attribute
+ * names the command that will be executed if the action
+ * is "site".
+ * @param siteCommand The siteCommand to set.
+ */
+ public void setSiteCommand(String siteCommand) {
+ this.siteCommand = siteCommand;
+ }
+ /**
+ * Sets the initialSiteCommand attribute. This attribute
+ * names a site command that will be executed immediately
+ * after connection.
+ * @param initialCommand The initialSiteCommand to set.
+ */
+ public void setInitialSiteCommand(String initialCommand) {
+ this.initialSiteCommand = initialCommand;
+ }
+
+ /**
+ * Whether to verify that data and control connections are
+ * connected to the same remote host.
+ *
+ * @since Ant 1.8.0
+ */
+ public void setEnableRemoteVerification(boolean b) {
+ enableRemoteVerification = b;
+ }
+
+ /**
+ * Checks to see that all required parameters are set.
+ *
+ * @throws BuildException if the configuration is not valid.
+ */
+ protected void checkAttributes() throws BuildException {
+ if (server == null) {
+ throw new BuildException("server attribute must be set!");
+ }
+ if (userid == null) {
+ throw new BuildException("userid attribute must be set!");
+ }
+ if (password == null) {
+ throw new BuildException("password attribute must be set!");
+ }
+
+ if ((action == LIST_FILES) && (listing == null)) {
+ throw new BuildException("listing attribute must be set for list "
+ + "action!");
+ }
+
+ if (action == MK_DIR && remotedir == null) {
+ throw new BuildException("remotedir attribute must be set for "
+ + "mkdir action!");
+ }
+
+ if (action == CHMOD && chmod == null) {
+ throw new BuildException("chmod attribute must be set for chmod "
+ + "action!");
+ }
+ if (action == SITE_CMD && siteCommand == null) {
+ throw new BuildException("sitecommand attribute must be set for site "
+ + "action!");
+ }
+
+
+ if (this.isConfigurationSet) {
+ try {
+ Class.forName("org.apache.commons.net.ftp.FTPClientConfig");
+ } catch (ClassNotFoundException e) {
+ throw new BuildException(
+ "commons-net.jar >= 1.4.0 is required for at least one"
+ + " of the attributes specified.");
+ }
+ }
+ }
+
+ /**
+ * Executable a retryable object.
+ * @param h the retry handler.
+ * @param r the object that should be retried until it succeeds
+ * or the number of retrys is reached.
+ * @param descr a description of the command that is being run.
+ * @throws IOException if there is a problem.
+ */
+ protected void executeRetryable(RetryHandler h, Retryable r, String descr)
+ throws IOException {
+ h.execute(r, descr);
+ }
+
+
+ /**
+ * For each file in the fileset, do the appropriate action: send, get,
+ * delete, or list.
+ *
+ * @param ftp the FTPClient instance used to perform FTP actions
+ * @param fs the fileset on which the actions are performed.
+ *
+ * @return the number of files to be transferred.
+ *
+ * @throws IOException if there is a problem reading a file
+ * @throws BuildException if there is a problem in the configuration.
+ */
+ protected int transferFiles(final FTPClient ftp, FileSet fs)
+ throws IOException, BuildException {
+ DirectoryScanner ds;
+ if (action == SEND_FILES) {
+ ds = fs.getDirectoryScanner(getProject());
+ } else {
+ ds = new FTPDirectoryScanner(ftp);
+ fs.setupDirectoryScanner(ds, getProject());
+ ds.setFollowSymlinks(fs.isFollowSymlinks());
+ ds.scan();
+ }
+
+ String[] dsfiles = null;
+ if (action == RM_DIR) {
+ dsfiles = ds.getIncludedDirectories();
+ } else {
+ dsfiles = ds.getIncludedFiles();
+ }
+ String dir = null;
+
+ if ((ds.getBasedir() == null)
+ && ((action == SEND_FILES) || (action == GET_FILES))) {
+ throw new BuildException("the dir attribute must be set for send "
+ + "and get actions");
+ } else {
+ if ((action == SEND_FILES) || (action == GET_FILES)) {
+ dir = ds.getBasedir().getAbsolutePath();
+ }
+ }
+
+ // If we are doing a listing, we need the output stream created now.
+ BufferedWriter bw = null;
+
+ try {
+ if (action == LIST_FILES) {
+ File pd = listing.getParentFile();
+
+ if (!pd.exists()) {
+ pd.mkdirs();
+ }
+ bw = new BufferedWriter(new FileWriter(listing));
+ }
+ RetryHandler h = new RetryHandler(this.retriesAllowed, this);
+ if (action == RM_DIR) {
+ // to remove directories, start by the end of the list
+ // the trunk does not let itself be removed before the leaves
+ for (int i = dsfiles.length - 1; i >= 0; i--) {
+ final String dsfile = dsfiles[i];
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ rmDir(ftp, dsfile);
+ }
+ }, dsfile);
+ }
+ } else {
+ final BufferedWriter fbw = bw;
+ final String fdir = dir;
+ if (this.newerOnly) {
+ this.granularityMillis =
+ this.timestampGranularity.getMilliseconds(action);
+ }
+ for (int i = 0; i < dsfiles.length; i++) {
+ final String dsfile = dsfiles[i];
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ switch (action) {
+ case SEND_FILES:
+ sendFile(ftp, fdir, dsfile);
+ break;
+ case GET_FILES:
+ getFile(ftp, fdir, dsfile);
+ break;
+ case DEL_FILES:
+ delFile(ftp, dsfile);
+ break;
+ case LIST_FILES:
+ listFile(ftp, fbw, dsfile);
+ break;
+ case CHMOD:
+ doSiteCommand(ftp, "chmod " + chmod
+ + " " + resolveFile(dsfile));
+ transferred++;
+ break;
+ default:
+ throw new BuildException("unknown ftp action " + action);
+ }
+ }
+ }, dsfile);
+ }
+ }
+ } finally {
+ FileUtils.close(bw);
+ }
+
+ return dsfiles.length;
+ }
+
+
+ /**
+ * Sends all files specified by the configured filesets to the remote
+ * server.
+ *
+ * @param ftp the FTPClient instance used to perform FTP actions
+ *
+ * @throws IOException if there is a problem reading a file
+ * @throws BuildException if there is a problem in the configuration.
+ */
+ protected void transferFiles(FTPClient ftp)
+ throws IOException, BuildException {
+ transferred = 0;
+ skipped = 0;
+
+ if (filesets.size() == 0) {
+ throw new BuildException("at least one fileset must be specified.");
+ } else {
+ // get files from filesets
+ final int size = filesets.size();
+ for (int i = 0; i < size; i++) {
+ FileSet fs = (FileSet) filesets.elementAt(i);
+
+ if (fs != null) {
+ transferFiles(ftp, fs);
+ }
+ }
+ }
+
+ log(transferred + " " + ACTION_TARGET_STRS[action] + " "
+ + COMPLETED_ACTION_STRS[action]);
+ if (skipped != 0) {
+ log(skipped + " " + ACTION_TARGET_STRS[action]
+ + " were not successfully " + COMPLETED_ACTION_STRS[action]);
+ }
+ }
+
+
+ /**
+ * Correct a file path to correspond to the remote host requirements. This
+ * implementation currently assumes that the remote end can handle
+ * Unix-style paths with forward-slash separators. This can be overridden
+ * with the <code>separator</code> task parameter. No attempt is made to
+ * determine what syntax is appropriate for the remote host.
+ *
+ * @param file the remote file name to be resolved
+ *
+ * @return the filename as it will appear on the server.
+ */
+ protected String resolveFile(String file) {
+ return file.replace(System.getProperty("file.separator").charAt(0),
+ remoteFileSep.charAt(0));
+ }
+
+
+ /**
+ * Creates all parent directories specified in a complete relative
+ * pathname. Attempts to create existing directories will not cause
+ * errors.
+ *
+ * @param ftp the FTP client instance to use to execute FTP actions on
+ * the remote server.
+ * @param filename the name of the file whose parents should be created.
+ * @throws IOException under non documented circumstances
+ * @throws BuildException if it is impossible to cd to a remote directory
+ *
+ */
+ protected void createParents(FTPClient ftp, String filename)
+ throws IOException, BuildException {
+
+ File dir = new File(filename);
+ if (dirCache.contains(dir)) {
+ return;
+ }
+
+ Vector parents = new Vector();
+ String dirname;
+
+ while ((dirname = dir.getParent()) != null) {
+ File checkDir = new File(dirname);
+ if (dirCache.contains(checkDir)) {
+ break;
+ }
+ dir = checkDir;
+ parents.addElement(dir);
+ }
+
+ // find first non cached dir
+ int i = parents.size() - 1;
+
+ if (i >= 0) {
+ String cwd = ftp.printWorkingDirectory();
+ String parent = dir.getParent();
+ if (parent != null) {
+ if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
+ throw new BuildException("could not change to "
+ + "directory: " + ftp.getReplyString());
+ }
+ }
+
+ while (i >= 0) {
+ dir = (File) parents.elementAt(i--);
+ // check if dir exists by trying to change into it.
+ if (!ftp.changeWorkingDirectory(dir.getName())) {
+ // could not change to it - try to create it
+ log("creating remote directory "
+ + resolveFile(dir.getPath()), Project.MSG_VERBOSE);
+ if (!ftp.makeDirectory(dir.getName())) {
+ handleMkDirFailure(ftp);
+ }
+ if (!ftp.changeWorkingDirectory(dir.getName())) {
+ throw new BuildException("could not change to "
+ + "directory: " + ftp.getReplyString());
+ }
+ }
+ dirCache.add(dir);
+ }
+ ftp.changeWorkingDirectory(cwd);
+ }
+ }
+ /**
+ * auto find the time difference between local and remote
+ * @param ftp handle to ftp client
+ * @return number of millis to add to remote time to make it comparable to local time
+ * @since ant 1.6
+ */
+ private long getTimeDiff(FTPClient ftp) {
+ long returnValue = 0;
+ File tempFile = findFileName(ftp);
+ try {
+ // create a local temporary file
+ FILE_UTILS.createNewFile(tempFile);
+ long localTimeStamp = tempFile.lastModified();
+ BufferedInputStream instream = new BufferedInputStream(new FileInputStream(tempFile));
+ ftp.storeFile(tempFile.getName(), instream);
+ instream.close();
+ boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
+ if (success) {
+ FTPFile [] ftpFiles = ftp.listFiles(tempFile.getName());
+ if (ftpFiles.length == 1) {
+ long remoteTimeStamp = ftpFiles[0].getTimestamp().getTime().getTime();
+ returnValue = localTimeStamp - remoteTimeStamp;
+ }
+ ftp.deleteFile(ftpFiles[0].getName());
+ }
+ // delegate the deletion of the local temp file to the delete task
+ // because of race conditions occurring on Windows
+ Delete mydelete = new Delete();
+ mydelete.bindToOwner(this);
+ mydelete.setFile(tempFile.getCanonicalFile());
+ mydelete.execute();
+ } catch (Exception e) {
+ throw new BuildException(e, getLocation());
+ }
+ return returnValue;
+ }
+ /**
+ * find a suitable name for local and remote temporary file
+ */
+ private File findFileName(FTPClient ftp) {
+ FTPFile [] theFiles = null;
+ final int maxIterations = 1000;
+ for (int counter = 1; counter < maxIterations; counter++) {
+ File localFile = FILE_UTILS.createTempFile(
+ "ant" + Integer.toString(counter), ".tmp",
+ null, false, false);
+ String fileName = localFile.getName();
+ boolean found = false;
+ try {
+ if (theFiles == null) {
+ theFiles = ftp.listFiles();
+ }
+ for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
+ if (theFiles[counter2] != null
+ && theFiles[counter2].getName().equals(fileName)) {
+ found = true;
+ break;
+ }
+ }
+ } catch (IOException ioe) {
+ throw new BuildException(ioe, getLocation());
+ }
+ if (!found) {
+ localFile.deleteOnExit();
+ return localFile;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Checks to see if the remote file is current as compared with the local
+ * file. Returns true if the target file is up to date.
+ * @param ftp ftpclient
+ * @param localFile local file
+ * @param remoteFile remote file
+ * @return true if the target file is up to date
+ * @throws IOException in unknown circumstances
+ * @throws BuildException if the date of the remote files cannot be found and the action is
+ * GET_FILES
+ */
+ protected boolean isUpToDate(FTPClient ftp, File localFile,
+ String remoteFile)
+ throws IOException, BuildException {
+ log("checking date for " + remoteFile, Project.MSG_VERBOSE);
+
+ FTPFile[] files = ftp.listFiles(remoteFile);
+
+ // For Microsoft's Ftp-Service an Array with length 0 is
+ // returned if configured to return listings in "MS-DOS"-Format
+ if (files == null || files.length == 0) {
+ // If we are sending files, then assume out of date.
+ // If we are getting files, then throw an error
+
+ if (action == SEND_FILES) {
+ log("Could not date test remote file: " + remoteFile
+ + "assuming out of date.", Project.MSG_VERBOSE);
+ return false;
+ } else {
+ throw new BuildException("could not date test remote file: "
+ + ftp.getReplyString());
+ }
+ }
+
+ long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
+ long localTimestamp = localFile.lastModified();
+ long adjustedRemoteTimestamp =
+ remoteTimestamp + this.timeDiffMillis + this.granularityMillis;
+
+ StringBuffer msg;
+ synchronized(TIMESTAMP_LOGGING_SDF) {
+ msg = new StringBuffer(" [")
+ .append(TIMESTAMP_LOGGING_SDF.format(new Date(localTimestamp)))
+ .append("] local");
+ }
+ log(msg.toString(), Project.MSG_VERBOSE);
+
+ synchronized(TIMESTAMP_LOGGING_SDF) {
+ msg = new StringBuffer(" [")
+ .append(TIMESTAMP_LOGGING_SDF.format(new Date(adjustedRemoteTimestamp)))
+ .append("] remote");
+ }
+ if (remoteTimestamp != adjustedRemoteTimestamp) {
+ synchronized(TIMESTAMP_LOGGING_SDF) {
+ msg.append(" - (raw: ")
+ .append(TIMESTAMP_LOGGING_SDF.format(new Date(remoteTimestamp)))
+ .append(")");
+ }
+ }
+ log(msg.toString(), Project.MSG_VERBOSE);
+
+
+
+ if (this.action == SEND_FILES) {
+ return adjustedRemoteTimestamp >= localTimestamp;
+ } else {
+ return localTimestamp >= adjustedRemoteTimestamp;
+ }
+ }
+
+
+ /**
+ * Sends a site command to the ftp server
+ * @param ftp ftp client
+ * @param theCMD command to execute
+ * @throws IOException in unknown circumstances
+ * @throws BuildException in unknown circumstances
+ */
+ protected void doSiteCommand(FTPClient ftp, String theCMD)
+ throws IOException, BuildException {
+ boolean rc;
+ String[] myReply = null;
+
+ log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
+
+ rc = ftp.sendSiteCommand(theCMD);
+
+ if (!rc) {
+ log("Failed to issue Site Command: " + theCMD, Project.MSG_WARN);
+ } else {
+
+ myReply = ftp.getReplyStrings();
+
+ for (int x = 0; x < myReply.length; x++) {
+ if (myReply[x] != null && myReply[x].indexOf("200") == -1) {
+ log(myReply[x], Project.MSG_WARN);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Sends a single file to the remote host. <code>filename</code> may
+ * contain a relative path specification. When this is the case, <code>sendFile</code>
+ * will attempt to create any necessary parent directories before sending
+ * the file. The file will then be sent using the entire relative path
+ * spec - no attempt is made to change directories. It is anticipated that
+ * this may eventually cause problems with some FTP servers, but it
+ * simplifies the coding.
+ * @param ftp ftp client
+ * @param dir base directory of the file to be sent (local)
+ * @param filename relative path of the file to be send
+ * locally relative to dir
+ * remotely relative to the remotedir attribute
+ * @throws IOException in unknown circumstances
+ * @throws BuildException in unknown circumstances
+ */
+ protected void sendFile(FTPClient ftp, String dir, String filename)
+ throws IOException, BuildException {
+ InputStream instream = null;
+
+ try {
+ // TODO - why not simply new File(dir, filename)?
+ File file = getProject().resolveFile(new File(dir, filename).getPath());
+
+ if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
+ return;
+ }
+
+ if (verbose) {
+ log("transferring " + file.getAbsolutePath());
+ }
+
+ instream = new BufferedInputStream(new FileInputStream(file));
+
+ createParents(ftp, filename);
+
+ ftp.storeFile(resolveFile(filename), instream);
+
+ boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
+
+ if (!success) {
+ String s = "could not put file: " + ftp.getReplyString();
+
+ if (skipFailedTransfers) {
+ log(s, Project.MSG_WARN);
+ skipped++;
+ } else {
+ throw new BuildException(s);
+ }
+
+ } else {
+ // see if we should issue a chmod command
+ if (chmod != null) {
+ doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(filename));
+ }
+ log("File " + file.getAbsolutePath() + " copied to " + server,
+ Project.MSG_VERBOSE);
+ transferred++;
+ }
+ } finally {
+ FileUtils.close(instream);
+ }
+ }
+
+
+ /**
+ * Delete a file from the remote host.
+ * @param ftp ftp client
+ * @param filename file to delete
+ * @throws IOException in unknown circumstances
+ * @throws BuildException if skipFailedTransfers is set to false
+ * and the deletion could not be done
+ */
+ protected void delFile(FTPClient ftp, String filename)
+ throws IOException, BuildException {
+ if (verbose) {
+ log("deleting " + filename);
+ }
+
+ if (!ftp.deleteFile(resolveFile(filename))) {
+ String s = "could not delete file: " + ftp.getReplyString();
+
+ if (skipFailedTransfers) {
+ log(s, Project.MSG_WARN);
+ skipped++;
+ } else {
+ throw new BuildException(s);
+ }
+ } else {
+ log("File " + filename + " deleted from " + server,
+ Project.MSG_VERBOSE);
+ transferred++;
+ }
+ }
+
+ /**
+ * Delete a directory, if empty, from the remote host.
+ * @param ftp ftp client
+ * @param dirname directory to delete
+ * @throws IOException in unknown circumstances
+ * @throws BuildException if skipFailedTransfers is set to false
+ * and the deletion could not be done
+ */
+ protected void rmDir(FTPClient ftp, String dirname)
+ throws IOException, BuildException {
+ if (verbose) {
+ log("removing " + dirname);
+ }
+
+ if (!ftp.removeDirectory(resolveFile(dirname))) {
+ String s = "could not remove directory: " + ftp.getReplyString();
+
+ if (skipFailedTransfers) {
+ log(s, Project.MSG_WARN);
+ skipped++;
+ } else {
+ throw new BuildException(s);
+ }
+ } else {
+ log("Directory " + dirname + " removed from " + server,
+ Project.MSG_VERBOSE);
+ transferred++;
+ }
+ }
+
+
+ /**
+ * Retrieve a single file from the remote host. <code>filename</code> may
+ * contain a relative path specification. <p>
+ *
+ * The file will then be retrieved using the entire relative path spec -
+ * no attempt is made to change directories. It is anticipated that this
+ * may eventually cause problems with some FTP servers, but it simplifies
+ * the coding.</p>
+ * @param ftp the ftp client
+ * @param dir local base directory to which the file should go back
+ * @param filename relative path of the file based upon the ftp remote directory
+ * and/or the local base directory (dir)
+ * @throws IOException in unknown circumstances
+ * @throws BuildException if skipFailedTransfers is false
+ * and the file cannot be retrieved.
+ */
+ protected void getFile(FTPClient ftp, String dir, String filename)
+ throws IOException, BuildException {
+ OutputStream outstream = null;
+ try {
+ File file = getProject().resolveFile(new File(dir, filename).getPath());
+
+ if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
+ return;
+ }
+
+ if (verbose) {
+ log("transferring " + filename + " to "
+ + file.getAbsolutePath());
+ }
+
+ File pdir = file.getParentFile();
+
+ if (!pdir.exists()) {
+ pdir.mkdirs();
+ }
+ outstream = new BufferedOutputStream(new FileOutputStream(file));
+ ftp.retrieveFile(resolveFile(filename), outstream);
+
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ String s = "could not get file: " + ftp.getReplyString();
+
+ if (skipFailedTransfers) {
+ log(s, Project.MSG_WARN);
+ skipped++;
+ } else {
+ throw new BuildException(s);
+ }
+
+ } else {
+ log("File " + file.getAbsolutePath() + " copied from "
+ + server, Project.MSG_VERBOSE);
+ transferred++;
+ if (preserveLastModified) {
+ outstream.close();
+ outstream = null;
+ FTPFile[] remote = ftp.listFiles(resolveFile(filename));
+ if (remote.length > 0) {
+ FILE_UTILS.setFileLastModified(file,
+ remote[0].getTimestamp()
+ .getTime().getTime());
+ }
+ }
+ }
+ } finally {
+ FileUtils.close(outstream);
+ }
+ }
+
+
+ /**
+ * List information about a single file from the remote host. <code>filename</code>
+ * may contain a relative path specification. <p>
+ *
+ * The file listing will then be retrieved using the entire relative path
+ * spec - no attempt is made to change directories. It is anticipated that
+ * this may eventually cause problems with some FTP servers, but it
+ * simplifies the coding.</p>
+ * @param ftp ftp client
+ * @param bw buffered writer
+ * @param filename the directory one wants to list
+ * @throws IOException in unknown circumstances
+ * @throws BuildException in unknown circumstances
+ */
+ protected void listFile(FTPClient ftp, BufferedWriter bw, String filename)
+ throws IOException, BuildException {
+ if (verbose) {
+ log("listing " + filename);
+ }
+ FTPFile[] ftpfiles = ftp.listFiles(resolveFile(filename));
+
+ if (ftpfiles != null && ftpfiles.length > 0) {
+ bw.write(ftpfiles[0].toString());
+ bw.newLine();
+ transferred++;
+ }
+ }
+
+
+ /**
+ * Create the specified directory on the remote host.
+ *
+ * @param ftp The FTP client connection
+ * @param dir The directory to create (format must be correct for host
+ * type)
+ * @throws IOException in unknown circumstances
+ * @throws BuildException if ignoreNoncriticalErrors has not been set to true
+ * and a directory could not be created, for instance because it was
+ * already existing. Precisely, the codes 521, 550 and 553 will trigger
+ * a BuildException
+ */
+ protected void makeRemoteDir(FTPClient ftp, String dir)
+ throws IOException, BuildException {
+ String workingDirectory = ftp.printWorkingDirectory();
+ if (verbose) {
+ if (dir.startsWith("/") || workingDirectory == null) {
+ log("Creating directory: " + dir + " in /");
+ } else {
+ log("Creating directory: " + dir + " in " + workingDirectory);
+ }
+ }
+ if (dir.startsWith("/")) {
+ ftp.changeWorkingDirectory("/");
+ }
+ String subdir = "";
+ StringTokenizer st = new StringTokenizer(dir, "/");
+ while (st.hasMoreTokens()) {
+ subdir = st.nextToken();
+ log("Checking " + subdir, Project.MSG_DEBUG);
+ if (!ftp.changeWorkingDirectory(subdir)) {
+ if (!ftp.makeDirectory(subdir)) {
+ // codes 521, 550 and 553 can be produced by FTP Servers
+ // to indicate that an attempt to create a directory has
+ // failed because the directory already exists.
+ int rc = ftp.getReplyCode();
+ if (!(ignoreNoncriticalErrors
+ && (rc == CODE_550 || rc == CODE_553
+ || rc == CODE_521))) {
+ throw new BuildException("could not create directory: "
+ + ftp.getReplyString());
+ }
+ if (verbose) {
+ log("Directory already exists");
+ }
+ } else {
+ if (verbose) {
+ log("Directory created OK");
+ }
+ ftp.changeWorkingDirectory(subdir);
+ }
+ }
+ }
+ if (workingDirectory != null) {
+ ftp.changeWorkingDirectory(workingDirectory);
+ }
+ }
+
+ /**
+ * look at the response for a failed mkdir action, decide whether
+ * it matters or not. If it does, we throw an exception
+ * @param ftp current ftp connection
+ * @throws BuildException if this is an error to signal
+ */
+ private void handleMkDirFailure(FTPClient ftp)
+ throws BuildException {
+ int rc = ftp.getReplyCode();
+ if (!(ignoreNoncriticalErrors
+ && (rc == CODE_550 || rc == CODE_553 || rc == CODE_521))) {
+ throw new BuildException("could not create directory: "
+ + ftp.getReplyString());
+ }
+ }
+
+ /**
+ * Runs the task.
+ *
+ * @throws BuildException if the task fails or is not configured
+ * correctly.
+ */
+ public void execute() throws BuildException {
+ checkAttributes();
+
+ FTPClient ftp = null;
+
+ try {
+ log("Opening FTP connection to " + server, Project.MSG_VERBOSE);
+
+ ftp = new FTPClient();
+ if (this.isConfigurationSet) {
+ ftp = FTPConfigurator.configure(ftp, this);
+ }
+
+ ftp.setRemoteVerificationEnabled(enableRemoteVerification);
+ ftp.connect(server, port);
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ throw new BuildException("FTP connection failed: "
+ + ftp.getReplyString());
+ }
+
+ log("connected", Project.MSG_VERBOSE);
+ log("logging in to FTP server", Project.MSG_VERBOSE);
+
+ if ((this.account != null && !ftp.login(userid, password, account))
+ || (this.account == null && !ftp.login(userid, password))) {
+ throw new BuildException("Could not login to FTP server");
+ }
+
+ log("login succeeded", Project.MSG_VERBOSE);
+
+ if (binary) {
+ ftp.setFileType(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ throw new BuildException("could not set transfer type: "
+ + ftp.getReplyString());
+ }
+ } else {
+ ftp.setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ throw new BuildException("could not set transfer type: "
+ + ftp.getReplyString());
+ }
+ }
+
+ if (passive) {
+ log("entering passive mode", Project.MSG_VERBOSE);
+ ftp.enterLocalPassiveMode();
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ throw new BuildException("could not enter into passive "
+ + "mode: " + ftp.getReplyString());
+ }
+ }
+
+ // If an initial command was configured then send it.
+ // Some FTP servers offer different modes of operation,
+ // E.G. switching between a UNIX file system mode and
+ // a legacy file system.
+ if (this.initialSiteCommand != null) {
+ RetryHandler h = new RetryHandler(this.retriesAllowed, this);
+ final FTPClient lftp = ftp;
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ doSiteCommand(lftp, FTP.this.initialSiteCommand);
+ }
+ }, "initial site command: " + this.initialSiteCommand);
+ }
+
+
+ // For a unix ftp server you can set the default mask for all files
+ // created.
+
+ if (umask != null) {
+ RetryHandler h = new RetryHandler(this.retriesAllowed, this);
+ final FTPClient lftp = ftp;
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ doSiteCommand(lftp, "umask " + umask);
+ }
+ }, "umask " + umask);
+ }
+
+ // If the action is MK_DIR, then the specified remote
+ // directory is the directory to create.
+
+ if (action == MK_DIR) {
+ RetryHandler h = new RetryHandler(this.retriesAllowed, this);
+ final FTPClient lftp = ftp;
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ makeRemoteDir(lftp, remotedir);
+ }
+ }, remotedir);
+ } else if (action == SITE_CMD) {
+ RetryHandler h = new RetryHandler(this.retriesAllowed, this);
+ final FTPClient lftp = ftp;
+ executeRetryable(h, new Retryable() {
+ public void execute() throws IOException {
+ doSiteCommand(lftp, FTP.this.siteCommand);
+ }
+ }, "Site Command: " + this.siteCommand);
+ } else {
+ if (remotedir != null) {
+ log("changing the remote directory to " + remotedir,
+ Project.MSG_VERBOSE);
+ ftp.changeWorkingDirectory(remotedir);
+ if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
+ throw new BuildException("could not change remote "
+ + "directory: " + ftp.getReplyString());
+ }
+ }
+ if (newerOnly && timeDiffAuto) {
+ // in this case we want to find how much time span there is between local
+ // and remote
+ timeDiffMillis = getTimeDiff(ftp);
+ }
+ log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
+ transferFiles(ftp);
+ }
+
+ } catch (IOException ex) {
+ throw new BuildException("error during FTP transfer: " + ex, ex);
+ } finally {
+ if (ftp != null && ftp.isConnected()) {
+ try {
+ log("disconnecting", Project.MSG_VERBOSE);
+ ftp.logout();
+ ftp.disconnect();
+ } catch (IOException ex) {
+ // ignore it
+ }
+ }
+ }
+ }
+
+
+ /**
+ * an action to perform, one of
+ * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
+ * "rmdir"
+ */
+ public static class Action extends EnumeratedAttribute {
+
+ private static final String[] VALID_ACTIONS = {
+ "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
+ "chmod", "rmdir", "site"
+ };
+
+
+ /**
+ * Get the valid values
+ *
+ * @return an array of the valid FTP actions.
+ */
+ public String[] getValues() {
+ return VALID_ACTIONS;
+ }
+
+
+ /**
+ * Get the symbolic equivalent of the action value.
+ *
+ * @return the SYMBOL representing the given action.
+ */
+ public int getAction() {
+ String actionL = getValue().toLowerCase(Locale.ENGLISH);
+ if (actionL.equals("send") || actionL.equals("put")) {
+ return SEND_FILES;
+ } else if (actionL.equals("recv") || actionL.equals("get")) {
+ return GET_FILES;
+ } else if (actionL.equals("del") || actionL.equals("delete")) {
+ return DEL_FILES;
+ } else if (actionL.equals("list")) {
+ return LIST_FILES;
+ } else if (actionL.equals("chmod")) {
+ return CHMOD;
+ } else if (actionL.equals("mkdir")) {
+ return MK_DIR;
+ } else if (actionL.equals("rmdir")) {
+ return RM_DIR;
+ } else if (actionL.equals("site")) {
+ return SITE_CMD;
+ }
+ return SEND_FILES;
+ }
+ }
+ /**
+ * represents one of the valid timestamp adjustment values
+ * recognized by the <code>timestampGranularity</code> attribute.<p>
+
+ * A timestamp adjustment may be used in file transfers for checking
+ * uptodateness. MINUTE means to add one minute to the server
+ * timestamp. This is done because FTP servers typically list
+ * timestamps HH:mm and client FileSystems typically use HH:mm:ss.
+ *
+ * The default is to use MINUTE for PUT actions and NONE for GET
+ * actions, since GETs have the <code>preserveLastModified</code>
+ * option, which takes care of the problem in most use cases where
+ * this level of granularity is an issue.
+ *
+ */
+ public static class Granularity extends EnumeratedAttribute {
+
+ private static final String[] VALID_GRANULARITIES = {
+ "", "MINUTE", "NONE"
+ };
+
+ /**
+ * Get the valid values.
+ * @return the list of valid Granularity values
+ */
+ public String[] getValues() {
+ return VALID_GRANULARITIES;
+ }
+ /**
+ * returns the number of milliseconds associated with
+ * the attribute, which can vary in some cases depending
+ * on the value of the action parameter.
+ * @param action SEND_FILES or GET_FILES
+ * @return the number of milliseconds associated with
+ * the attribute, in the context of the supplied action
+ */
+ public long getMilliseconds(int action) {
+ String granularityU = getValue().toUpperCase(Locale.ENGLISH);
+ if ("".equals(granularityU)) {
+ if (action == SEND_FILES) {
+ return GRANULARITY_MINUTE;
+ }
+ } else if ("MINUTE".equals(granularityU)) {
+ return GRANULARITY_MINUTE;
+ }
+ return 0L;
+ }
+ static final Granularity getDefault() {
+ Granularity g = new Granularity();
+ g.setValue("");
+ return g;
+ }
+
+ }
+ /**
+ * one of the valid system type keys recognized by the systemTypeKey
+ * attribute.
+ */
+ public static class FTPSystemType extends EnumeratedAttribute {
+
+ private static final String[] VALID_SYSTEM_TYPES = {
+ "", "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400",
+ "MVS"
+ };
+
+
+ /**
+ * Get the valid values.
+ * @return the list of valid system types.
+ */
+ public String[] getValues() {
+ return VALID_SYSTEM_TYPES;
+ }
+
+ static final FTPSystemType getDefault() {
+ FTPSystemType ftpst = new FTPSystemType();
+ ftpst.setValue("");
+ return ftpst;
+ }
+ }
+ /**
+ * Enumerated class for languages.
+ */
+ public static class LanguageCode extends EnumeratedAttribute {
+
+
+ private static final String[] VALID_LANGUAGE_CODES =
+ getValidLanguageCodes();
+
+ private static String[] getValidLanguageCodes() {
+ Collection c = FTPClientConfig.getSupportedLanguageCodes();
+ String[] ret = new String[c.size() + 1];
+ int i = 0;
+ ret[i++] = "";
+ for (Iterator it = c.iterator(); it.hasNext(); i++) {
+ ret[i] = (String) it.next();
+ }
+ return ret;
+ }
+
+
+ /**
+ * Return the value values.
+ * @return the list of valid language types.
+ */
+ public String[] getValues() {
+ return VALID_LANGUAGE_CODES;
+ }
+
+ static final LanguageCode getDefault() {
+ LanguageCode lc = new LanguageCode();
+ lc.setValue("");
+ return lc;
+ }
+ }
+
+}