ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bugzi...@apache.org
Subject DO NOT REPLY [Bug 27883] New: - FTP task: possible enhancement, re-connect feature
Date Tue, 23 Mar 2004 22:17:33 GMT
DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG 
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://issues.apache.org/bugzilla/show_bug.cgi?id=27883>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND 
INSERTED IN THE BUG DATABASE.

http://issues.apache.org/bugzilla/show_bug.cgi?id=27883

FTP task: possible enhancement, re-connect feature

           Summary: FTP task: possible enhancement, re-connect feature
           Product: Ant
           Version: 1.6.1
          Platform: All
        OS/Version: Other
            Status: NEW
          Severity: Enhancement
          Priority: Other
         Component: Optional Tasks
        AssignedTo: dev@ant.apache.org
        ReportedBy: j.peer@gmx.net


I ran into the problem that my FTP server (runs on windows) resetted the
connections quite frequently , resulting in premature abort of the FTP task and
subsequent build failure.

Ant version: 1.6.1
Commons-net: 1.1.0 and CVS checkout on march 23, 2004
Client System: JSDK 1.4, win XP
Server System: Win2k, various FTP servers

Note: the connection resets happened on all the FTP clients and servers i tried;
most likely it's some network issue, which i cannot control or fix. 

Therefore, i slightly adjusted the sourcecode of
org.apache.tools.ant.taskdefs.optional.net.FTP to force it to re-connect to the
server in case of an error, just like many of the GUI based FTP clients do (e.g.
SmartFTP).

Further, I have added an attribute "maxAttempts" to define how often the FTP
task should re-connect/retry before it gives up (default value is 1, to retain
un-patched behavior).

i've used it for a couple of hours now (only for file PUTing though) and it
works well, allowing me to continue my work despite the very fragile FTP connection.

================================================================ 

ATTACHEMENT: source code of org.apache.tools.ant.taskdefs.optional.net.FTP -  my
changes are marked with "JPEER", if you want to skim through...

================================================================ 


/*
 * Copyright  2000-2004 The Apache Software Foundation
 *
 *  Licensed 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 org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
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.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Vector;

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;

/**
 * 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.
 *
 * @author Roger Vaughn <a href="mailto:rvaughn@seaconinc.com">
 *      rvaughn@seaconinc.com</a>
 * @author Glenn McAllister <a href="mailto:glennm@ca.ibm.com">
 *      glennm@ca.ibm.com</a>
 * @author Magesh Umasankar
 * @author <a href="mailto:kadams@gfs.com">Kyle Adams</a>
 * @since Ant 1.3
 */
public class FTP
     extends Task {
    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;
    /** return code of ftp - not implemented in commons-net version 1.0 */
    private static final int CODE_521 = 521;
    /** Default port for FTP */
    public static final int DEFAULT_FTP_PORT = 21;

    private String remotedir;
    private String server;
    private String userid;
    private String password;
    private File listing;
    private boolean binary = true;
    private boolean passive = false;
    private boolean verbose = false;
    private boolean newerOnly = false;
    private long timeDiffMillis = 0;
    private boolean timeDiffAuto = false;
    private int action = SEND_FILES;
    private Vector filesets = new Vector();
    private Vector dirCache = new Vector();
    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 FileUtils fileUtils = FileUtils.newFileUtils();
		// JPEER
		private int maxAttempts = 1;
		
    protected static final String[] ACTION_STRS = {
        "sending",
        "getting",
        "deleting",
        "listing",
        "making directory",
        "chmod",
        "removing"
        };

    protected static final String[] COMPLETED_ACTION_STRS = {
        "sent",
        "retrieved",
        "deleted",
        "listed",
        "created directory",
        "mode changed",
        "removed"
        };

    protected static final String[] ACTION_TARGET_STRS = {
        "files",
        "files",
        "files",
        "files",
        "directory",
        "files",
        "directories"
        };


    /**
     * 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 {
        protected FTPClient ftp = null;
        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 Vector();
            filesNotIncluded = new Vector();
            filesExcluded = new Vector();
            dirsIncluded = new Vector();
            dirsNotIncluded = new Vector();
            dirsExcluded = new Vector();

            try {
                String cwd = ftp.printWorkingDirectory();
                // always start from the current ftp working dir

                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()) {
                        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
         * @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.getName().equals(".")
                         && !file.getName().equals("..")) {
                        if (isFunctioningAsDirectory(ftp, dir, file)) {
                            String name = vpath + file.getName();
                            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 {
                            String name = vpath + file.getName();
                            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)) {
                        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 Set scannedDirs = new HashSet();

        /**
         * Has the directory with the given path relative to the base
         * directory already been scanned?
         *
         * <p>Registers the given directory as scanned as a side effect.</p>
         *
         * @since Ant 1.6
         */
        private boolean hasBeenScanned(String vpath) {
            return !scannedDirs.add(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;
        }
        /**
         * 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].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].getName().equals(target) &&
pcounter != icounter) {
                                candidateFound = false;
                            }
                        }
                        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);
                }
                for (int fcount = 0; fcount < pathElements.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 = this.curpwd + remoteFileSep
                            + 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(pathElements.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].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 curpwd + remoteFileSep + 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.indexOf(rootPath + remoteFileSep) == 0) {
                    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 thge 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;
                for (int pcount = pathElements2.size(); pcount <
pathElements.size(); pcount++) {
                    String currentElement = (String) pathElements.elementAt(pcount);
                    FTPFile[] theFiles = listFiles(currentPath);
                    FTPFile theFile = null;
                    if (theFiles != null) {
                        theFile = getFile(theFiles, currentElement);
                    }
                    if (theFile == null) {
                        throw new BuildException("could not find " + currentElement
                            + " from " + currentPath);
                    } else {
                        traversesSymlinks = traversesSymlinks ||
theFile.isSymbolicLink();
                        if (!relPath.equals("")) {
                            relPath = relPath + remoteFileSep;
                        }
                        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].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;
            }
            /**
             * 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
                    String relpath = getRelativePath();
                }
                return traversesSymlinks;
            }
        }
        /**
         * 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;
    }


    /**
     * 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;
    }

		
		
    /**
     * JPEER
		 * Sets the limit of re-connects on error. Useful for instable connections.
     * 
     * @param maxAttempts, an integer number equal or greather than 1 
     */
    public void setMaxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }
		
		

    /**
     *  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" and "list".
     *
     * @deprecated 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" and "list".
     *
     * @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;
    }


    /**
     * Checks to see that all required parameters are set.
     *
     * @throws BuildException if the configuration is not valid.
     */
    protected void checkConfiguration() 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!");
        }
    }


    /**
     * 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(FTPClient ftp, FileSet fs)
         throws IOException, BuildException {
        DirectoryScanner ds;

        if (action == SEND_FILES) {
            ds = fs.getDirectoryScanner(getProject());
        } else {
            // warn that selectors are not supported
            if (fs.getSelectors(getProject()).length != 0) {
                getProject().log("selectors are not supported in remote filesets",
                    Project.MSG_WARN);
            }
            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 = fileUtils.getParentFile(listing);

                if (!pd.exists()) {
                    pd.mkdirs();
                }
                bw = new BufferedWriter(new FileWriter(listing));
            }
            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--) {
                    rmDir(ftp, dsfiles[i]);
                }
            }   else {
                for (int i = 0; i < dsfiles.length; i++) {
									// JPEER
									// don't quit until transfer success or maxAttempts exceeded
								  boolean success = false;
									int attempts = 0;
									while(!success) {
										if(attempts > 0) { log("attempt nr. "+(attempts+1)+"..."); } 
										try {
												switch (action) {
														case SEND_FILES:
																sendFile(ftp, dir, dsfiles[i]);
																success = true;
																break;
														case GET_FILES:
																getFile(ftp, dir, dsfiles[i]);
																success = true;
																break;
														case DEL_FILES:
																delFile(ftp, dsfiles[i]);
																success = true;
																break;
														case LIST_FILES:
																listFile(ftp, bw, dsfiles[i]);
																success = true;
																break;
														case CHMOD:
																doSiteCommand(ftp, "chmod " + chmod + " " +
resolveFile(dsfiles[i]));
																success = true;
																transferred++;
																break;
														default:
																throw new BuildException("unknown ftp action " + action);
												}
										} catch(IOException ioe) {
											attempts++;
											if(attempts >= maxAttempts) {
												log("maxAttempts exceeded, aborting.");
												throw ioe;
											}
											// get ready for next attempt
											ftp = initFTPClient();
											prepareForTransfer(ftp);											
										}
									}
                }
            }
        } finally {
            if (bw != null) {
                bw.close();
            }
        }

        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
            for (int i = 0; i < filesets.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.addElement(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
            fileUtils.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 = remoteTimeStamp - localTimeStamp;
                }
                ftp.deleteFile(ftpFiles[0].getName());
            }
            // delegate the deletion of the local temp file to the delete task
            // because of race conditions occuring on Windows
            Delete mydelete = (Delete) getProject().createTask("delete");
            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 = fileUtils.createTempFile("ant" +
Integer.toString(counter), ".tmp",
                null);
            String fileName = localFile.getName();
            boolean found = false;
            try {
                if (counter == 1) {
                    theFiles = ftp.listFiles();
                }
                for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
                    if (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();

        if (this.action == SEND_FILES) {
            return remoteTimestamp + timeDiffMillis > localTimestamp;
        } else {
            return localTimestamp > remoteTimestamp + timeDiffMillis;
        }
    }


    /**
    * 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].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 {
            // XXX - 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 {
            if (instream != null) {
                try {
                    instream.close();
                } catch (IOException ex) {
                    // ignore it
                }
            }
        }
    }


    /**
     * 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 retreived 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 = fileUtils.getParentFile(file);

            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) {
                        fileUtils.setFileLastModified(file,
                                                      remote[0].getTimestamp()
                                                      .getTime().getTime());
                    }
                }
            }
        } finally {
            if (outstream != null) {
                try {
                    outstream.close();
                } catch (IOException ex) {
                    // ignore it
                }
            }
        }
    }


    /**
     * 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 ftpfile = ftp.listFiles(resolveFile(filename))[0];

        bw.write(ftpfile.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) {
            log("Creating directory: " + dir);
        }
        if (dir.indexOf("/") == 0) {
            ftp.changeWorkingDirectory("/");
        }
        String subdir = new String();
        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 == FTPReply.CODE_550 || rc == FTPReply.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 == FTPReply.CODE_550 || rc == FTPReply.CODE_553 || rc ==
CODE_521))) {
            throw new BuildException("could not create directory: "
                + ftp.getReplyString());
        }
    }

	// JPEER
	// just some refacturing to avoid redundant code in the patch
	// (this is method is needed both during start and during re-connect)
	protected FTPClient initFTPClient() throws IOException {

            log("Opening FTP connection to " + server, Project.MSG_VERBOSE);

            FTPClient ftp = new FTPClient();

            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 (!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.IMAGE_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());
                }
            }

            // For a unix ftp server you can set the default mask for all files
            // created.

            if (umask != null) {
                doSiteCommand(ftp, "umask " + umask);
            }

            // If the action is MK_DIR, then the specified remote
            // directory is the directory to create.

						
			return ftp;
	}
		
	// JPEER
	// just some refacturing to avoid redundant code in the patch
	// (this is method is needed both during start and during re-connect)		
		protected void prepareForTransfer(FTPClient ftp) throws IOException {
			if (remotedir != null) {
					log("changing the remote directory", 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);
			}			
		}
		
		
		
    /**
     * Runs the task.
     *
     * @throws BuildException if the task fails or is not configured
     *         correctly.
     */
    public void execute() throws BuildException {
        checkConfiguration();

        FTPClient ftp = null; 

				try {
					  // JPEER 
						ftp = initFTPClient();
					
            if (action == MK_DIR) {
                makeRemoteDir(ftp, remotedir);
            } else {
							  // JPEER
								prepareForTransfer(ftp);
                log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
                transferFiles(ftp);
            }

        } catch (IOException ex) {
            throw new BuildException("error during FTP transfer: " + 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"
            };


        /**
         * 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.US);

            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;
            }
            return SEND_FILES;
        }
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org


Mime
View raw message