alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Ant example source code file (FTP.java)

This example Ant source code file (FTP.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Ant tags/keywords

antftpfile, buildexception, buildexception, file, ftp, ftpclient, ftpfile, ftpfile, io, ioexception, ioexception, send_files, string, string, text, util, vector

The FTP.java source code

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

/**
 * Basic FTP client. Performs the following actions:
 * <ul>
 *   <li> send - send files to a remote server. This is the
 *   default action.</li>
 *   <li> get - retrieve files from a remote server.
 *   <li> del - delete files from a remote server.
 *   <li> list - create a file listing.
 *   <li> chmod - change unix file permissions.
 *   <li> rmdir - remove directories, if empty, from a
 *   remote server.</li>
 * </ul>
 * <strong>Note: 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 {
    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 - not implemented in commons-net version 1.0 */
    private static final int CODE_521 = 521;

    /** adjust uptodate calculations where server timestamps are HH:mm and client's
     * are HH:mm:ss */
    private static final long GRANULARITY_MINUTE = 60000L;

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

    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 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 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
                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
         * @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.

* * @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; } 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].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 (!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].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 for directories */ public boolean isDirectory() { return ftpFile.isDirectory(); } /** * tell if a file is a symbolic link * @return <code>true 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 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. 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 * * @param timeDiffMillis number of milliseconds * * @since ant 1.6 */ public void setTimeDiffMillis(long timeDiffMillis) { this.timeDiffMillis = timeDiffMillis; } /** * "true" 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 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 && !serverLanguageCode.equals("")) { 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. */ String getSystemTypeKey() { return systemTypeKey.getValue(); } /** * @return Returns the defaultDateFormatConfig. */ String getDefaultDateFormatConfig() { return defaultDateFormatConfig; } /** * @return Returns the recentDateFormatConfig. */ String getRecentDateFormatConfig() { return recentDateFormatConfig; } /** * @return Returns the serverLanguageCodeConfig. */ String getServerLanguageCodeConfig() { return serverLanguageCodeConfig.getValue(); } /** * @return Returns the serverTimeZoneConfig. */ String getServerTimeZoneConfig() { return serverTimeZoneConfig; } /** * @return Returns the shortMonthNamesConfig. */ 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)) { 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; } /** * 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 hander. * @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 { // 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 = 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 { 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 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 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 occuring 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); 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; } private static final SimpleDateFormat TIMESTAMP_LOGGING_SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 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 = new StringBuffer(" [") .append(TIMESTAMP_LOGGING_SDF.format(new Date(localTimestamp))) .append("] local"); log(msg.toString(), Project.MSG_VERBOSE); msg = new StringBuffer(" [") .append(TIMESTAMP_LOGGING_SDF.format(new Date(adjustedRemoteTimestamp))) .append("] remote"); if (remoteTimestamp != adjustedRemoteTimestamp) { 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].indexOf("200") == -1) { log(myReply[x], Project.MSG_WARN); } } } } /** * Sends a single file to the remote host. <code>filename may * contain a relative path specification. When this is the case, <code>sendFile * 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 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 = 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 { if (outstream != null) { try { outstream.close(); } catch (IOException ex) { // ignore it } } } } /** * List information about a single file from the remote host. <code>filename * 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) { 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()); } } /** * 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.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.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()); } } // 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", 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.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; } else if (actionL.equals("site")) { return SITE_CMD; } return SEND_FILES; } } /** * represents one of the valid timestamp adjustment values * recognized by the <code>timestampGranularity attribute.

* 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 * 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() { // TODO Auto-generated method stub 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.US); 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; } } }

Other Ant examples (source code examples)

Here is a short list of links related to this Ant FTP.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.