0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018: package org.apache.tools.ant.taskdefs.optional.net;
0019:
0020: import java.io.BufferedInputStream;
0021: import java.io.BufferedOutputStream;
0022: import java.io.BufferedWriter;
0023: import java.io.File;
0024: import java.io.FileInputStream;
0025: import java.io.FileOutputStream;
0026: import java.io.FileWriter;
0027: import java.io.IOException;
0028: import java.io.InputStream;
0029: import java.io.OutputStream;
0030: import java.text.SimpleDateFormat;
0031: import java.util.Collection;
0032: import java.util.Date;
0033: import java.util.Enumeration;
0034: import java.util.HashMap;
0035: import java.util.HashSet;
0036: import java.util.Hashtable;
0037: import java.util.Iterator;
0038: import java.util.Locale;
0039: import java.util.Map;
0040: import java.util.Set;
0041: import java.util.StringTokenizer;
0042: import java.util.Vector;
0043:
0044: import org.apache.commons.net.ftp.FTPClient;
0045: import org.apache.commons.net.ftp.FTPClientConfig;
0046: import org.apache.commons.net.ftp.FTPFile;
0047: import org.apache.commons.net.ftp.FTPReply;
0048: import org.apache.tools.ant.BuildException;
0049: import org.apache.tools.ant.DirectoryScanner;
0050: import org.apache.tools.ant.Project;
0051: import org.apache.tools.ant.Task;
0052: import org.apache.tools.ant.taskdefs.Delete;
0053: import org.apache.tools.ant.types.EnumeratedAttribute;
0054: import org.apache.tools.ant.types.FileSet;
0055: import org.apache.tools.ant.types.selectors.SelectorUtils;
0056: import org.apache.tools.ant.util.FileUtils;
0057: import org.apache.tools.ant.util.RetryHandler;
0058: import org.apache.tools.ant.util.Retryable;
0059:
0060: /**
0061: * Basic FTP client. Performs the following actions:
0062: * <ul>
0063: * <li> <strong>send</strong> - send files to a remote server. This is the
0064: * default action.</li>
0065: * <li> <strong>get</strong> - retrieve files from a remote server.</li>
0066: * <li> <strong>del</strong> - delete files from a remote server.</li>
0067: * <li> <strong>list</strong> - create a file listing.</li>
0068: * <li> <strong>chmod</strong> - change unix file permissions.</li>
0069: * <li> <strong>rmdir</strong> - remove directories, if empty, from a
0070: * remote server.</li>
0071: * </ul>
0072: * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
0073: * to hold data ports open after a "retr" operation, allowing them to timeout
0074: * instead of shutting them down cleanly. This happens in active or passive
0075: * mode, and the ports will remain open even after ending the FTP session. FTP
0076: * "send" operations seem to close ports immediately. This behavior may cause
0077: * problems on some systems when downloading large sets of files.
0078: *
0079: * @since Ant 1.3
0080: */
0081: public class FTP extends Task {
0082: protected static final int SEND_FILES = 0;
0083: protected static final int GET_FILES = 1;
0084: protected static final int DEL_FILES = 2;
0085: protected static final int LIST_FILES = 3;
0086: protected static final int MK_DIR = 4;
0087: protected static final int CHMOD = 5;
0088: protected static final int RM_DIR = 6;
0089: protected static final int SITE_CMD = 7;
0090: /** return code of ftp - not implemented in commons-net version 1.0 */
0091: private static final int CODE_521 = 521;
0092:
0093: /** adjust uptodate calculations where server timestamps are HH:mm and client's
0094: * are HH:mm:ss */
0095: private static final long GRANULARITY_MINUTE = 60000L;
0096:
0097: /** Default port for FTP */
0098: public static final int DEFAULT_FTP_PORT = 21;
0099:
0100: private static final FileUtils FILE_UTILS = FileUtils
0101: .getFileUtils();
0102:
0103: private String remotedir;
0104: private String server;
0105: private String userid;
0106: private String password;
0107: private String account;
0108: private File listing;
0109: private boolean binary = true;
0110: private boolean passive = false;
0111: private boolean verbose = false;
0112: private boolean newerOnly = false;
0113: private long timeDiffMillis = 0;
0114: private long granularityMillis = 0L;
0115: private boolean timeDiffAuto = false;
0116: private int action = SEND_FILES;
0117: private Vector filesets = new Vector();
0118: private Vector dirCache = new Vector();
0119: private int transferred = 0;
0120: private String remoteFileSep = "/";
0121: private int port = DEFAULT_FTP_PORT;
0122: private boolean skipFailedTransfers = false;
0123: private int skipped = 0;
0124: private boolean ignoreNoncriticalErrors = false;
0125: private boolean preserveLastModified = false;
0126: private String chmod = null;
0127: private String umask = null;
0128: private FTPSystemType systemTypeKey = FTPSystemType.getDefault();
0129: private String defaultDateFormatConfig = null;
0130: private String recentDateFormatConfig = null;
0131: private LanguageCode serverLanguageCodeConfig = LanguageCode
0132: .getDefault();
0133: private String serverTimeZoneConfig = null;
0134: private String shortMonthNamesConfig = null;
0135: private Granularity timestampGranularity = Granularity.getDefault();
0136: private boolean isConfigurationSet = false;
0137: private int retriesAllowed = 0;
0138: private String siteCommand = null;
0139: private String initialSiteCommand = null;
0140:
0141: protected static final String[] ACTION_STRS = { "sending",
0142: "getting", "deleting", "listing", "making directory",
0143: "chmod", "removing", "site" };
0144:
0145: protected static final String[] COMPLETED_ACTION_STRS = { "sent",
0146: "retrieved", "deleted", "listed", "created directory",
0147: "mode changed", "removed", "site command executed" };
0148:
0149: protected static final String[] ACTION_TARGET_STRS = { "files",
0150: "files", "files", "files", "directory", "files",
0151: "directories", "site command" };
0152:
0153: /**
0154: * internal class allowing to read the contents of a remote file system
0155: * using the FTP protocol
0156: * used in particular for ftp get operations
0157: * differences with DirectoryScanner
0158: * "" (the root of the fileset) is never included in the included directories
0159: * followSymlinks defaults to false
0160: */
0161: protected class FTPDirectoryScanner extends DirectoryScanner {
0162: // CheckStyle:VisibilityModifier OFF - bc
0163: protected FTPClient ftp = null;
0164: // CheckStyle:VisibilityModifier ON
0165:
0166: private String rootPath = null;
0167:
0168: /**
0169: * since ant 1.6
0170: * this flag should be set to true on UNIX and can save scanning time
0171: */
0172: private boolean remoteSystemCaseSensitive = false;
0173: private boolean remoteSensitivityChecked = false;
0174:
0175: /**
0176: * constructor
0177: * @param ftp ftpclient object
0178: */
0179: public FTPDirectoryScanner(FTPClient ftp) {
0180: super ();
0181: this .ftp = ftp;
0182: this .setFollowSymlinks(false);
0183: }
0184:
0185: /**
0186: * scans the remote directory,
0187: * storing internally the included files, directories, ...
0188: */
0189: public void scan() {
0190: if (includes == null) {
0191: // No includes supplied, so set it to 'matches all'
0192: includes = new String[1];
0193: includes[0] = "**";
0194: }
0195: if (excludes == null) {
0196: excludes = new String[0];
0197: }
0198:
0199: filesIncluded = new Vector();
0200: filesNotIncluded = new Vector();
0201: filesExcluded = new Vector();
0202: dirsIncluded = new Vector();
0203: dirsNotIncluded = new Vector();
0204: dirsExcluded = new Vector();
0205:
0206: try {
0207: String cwd = ftp.printWorkingDirectory();
0208: // always start from the current ftp working dir
0209: forceRemoteSensitivityCheck();
0210:
0211: checkIncludePatterns();
0212: clearCaches();
0213: ftp.changeWorkingDirectory(cwd);
0214: } catch (IOException e) {
0215: throw new BuildException("Unable to scan FTP server: ",
0216: e);
0217: }
0218: }
0219:
0220: /**
0221: * this routine is actually checking all the include patterns in
0222: * order to avoid scanning everything under base dir
0223: * @since ant1.6
0224: */
0225: private void checkIncludePatterns() {
0226:
0227: Hashtable newroots = new Hashtable();
0228: // put in the newroots vector the include patterns without
0229: // wildcard tokens
0230: for (int icounter = 0; icounter < includes.length; icounter++) {
0231: String newpattern = SelectorUtils
0232: .rtrimWildcardTokens(includes[icounter]);
0233: newroots.put(newpattern, includes[icounter]);
0234: }
0235: if (remotedir == null) {
0236: try {
0237: remotedir = ftp.printWorkingDirectory();
0238: } catch (IOException e) {
0239: throw new BuildException(
0240: "could not read current ftp directory",
0241: getLocation());
0242: }
0243: }
0244: AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
0245: rootPath = baseFTPFile.getAbsolutePath();
0246: // construct it
0247: if (newroots.containsKey("")) {
0248: // we are going to scan everything anyway
0249: scandir(rootPath, "", true);
0250: } else {
0251: // only scan directories that can include matched files or
0252: // directories
0253: Enumeration enum2 = newroots.keys();
0254:
0255: while (enum2.hasMoreElements()) {
0256: String currentelement = (String) enum2
0257: .nextElement();
0258: String originalpattern = (String) newroots
0259: .get(currentelement);
0260: AntFTPFile myfile = new AntFTPFile(baseFTPFile,
0261: currentelement);
0262: boolean isOK = true;
0263: boolean traversesSymlinks = false;
0264: String path = null;
0265:
0266: if (myfile.exists()) {
0267: forceRemoteSensitivityCheck();
0268: if (remoteSensitivityChecked
0269: && remoteSystemCaseSensitive
0270: && isFollowSymlinks()) {
0271: // cool case,
0272: //we do not need to scan all the subdirs in the relative path
0273: path = myfile.getFastRelativePath();
0274: } else {
0275: // may be on a case insensitive file system. We want
0276: // the results to show what's really on the disk, so
0277: // we need to double check.
0278: try {
0279: path = myfile.getRelativePath();
0280: traversesSymlinks = myfile
0281: .isTraverseSymlinks();
0282: } catch (IOException be) {
0283: throw new BuildException(be,
0284: getLocation());
0285: } catch (BuildException be) {
0286: isOK = false;
0287: }
0288: }
0289: } else {
0290: isOK = false;
0291: }
0292: if (isOK) {
0293: currentelement = path.replace(remoteFileSep
0294: .charAt(0), File.separatorChar);
0295: if (!isFollowSymlinks() && traversesSymlinks) {
0296: continue;
0297: }
0298:
0299: if (myfile.isDirectory()) {
0300: if (isIncluded(currentelement)
0301: && currentelement.length() > 0) {
0302: accountForIncludedDir(currentelement,
0303: myfile, true);
0304: } else {
0305: if (currentelement.length() > 0) {
0306: if (currentelement
0307: .charAt(currentelement
0308: .length() - 1) != File.separatorChar) {
0309: currentelement = currentelement
0310: + File.separatorChar;
0311: }
0312: }
0313: scandir(myfile.getAbsolutePath(),
0314: currentelement, true);
0315: }
0316: } else {
0317: if (isCaseSensitive
0318: && originalpattern
0319: .equals(currentelement)) {
0320: accountForIncludedFile(currentelement);
0321: } else if (!isCaseSensitive
0322: && originalpattern
0323: .equalsIgnoreCase(currentelement)) {
0324: accountForIncludedFile(currentelement);
0325: }
0326: }
0327: }
0328: }
0329: }
0330: }
0331:
0332: /**
0333: * scans a particular directory
0334: * @param dir directory to scan
0335: * @param vpath relative path to the base directory of the remote fileset
0336: * always ended with a File.separator
0337: * @param fast seems to be always true in practice
0338: */
0339: protected void scandir(String dir, String vpath, boolean fast) {
0340: // avoid double scanning of directories, can only happen in fast mode
0341: if (fast && hasBeenScanned(vpath)) {
0342: return;
0343: }
0344: try {
0345: if (!ftp.changeWorkingDirectory(dir)) {
0346: return;
0347: }
0348: String completePath = null;
0349: if (!vpath.equals("")) {
0350: completePath = rootPath
0351: + remoteFileSep
0352: + vpath.replace(File.separatorChar,
0353: remoteFileSep.charAt(0));
0354: } else {
0355: completePath = rootPath;
0356: }
0357: FTPFile[] newfiles = listFiles(completePath, false);
0358:
0359: if (newfiles == null) {
0360: ftp.changeToParentDirectory();
0361: return;
0362: }
0363: for (int i = 0; i < newfiles.length; i++) {
0364: FTPFile file = newfiles[i];
0365: if (!file.getName().equals(".")
0366: && !file.getName().equals("..")) {
0367: if (isFunctioningAsDirectory(ftp, dir, file)) {
0368: String name = vpath + file.getName();
0369: boolean slowScanAllowed = true;
0370: if (!isFollowSymlinks()
0371: && file.isSymbolicLink()) {
0372: dirsExcluded.addElement(name);
0373: slowScanAllowed = false;
0374: } else if (isIncluded(name)) {
0375: accountForIncludedDir(name,
0376: new AntFTPFile(ftp, file,
0377: completePath), fast);
0378: } else {
0379: dirsNotIncluded.addElement(name);
0380: if (fast && couldHoldIncluded(name)) {
0381: scandir(file.getName(), name
0382: + File.separator, fast);
0383: }
0384: }
0385: if (!fast && slowScanAllowed) {
0386: scandir(file.getName(), name
0387: + File.separator, fast);
0388: }
0389: } else {
0390: String name = vpath + file.getName();
0391: if (!isFollowSymlinks()
0392: && file.isSymbolicLink()) {
0393: filesExcluded.addElement(name);
0394: } else if (isFunctioningAsFile(ftp, dir,
0395: file)) {
0396: accountForIncludedFile(name);
0397: }
0398: }
0399: }
0400: }
0401: ftp.changeToParentDirectory();
0402: } catch (IOException e) {
0403: throw new BuildException(
0404: "Error while communicating with FTP "
0405: + "server: ", e);
0406: }
0407: }
0408:
0409: /**
0410: * process included file
0411: * @param name path of the file relative to the directory of the fileset
0412: */
0413: private void accountForIncludedFile(String name) {
0414: if (!filesIncluded.contains(name)
0415: && !filesExcluded.contains(name)) {
0416:
0417: if (isIncluded(name)) {
0418: if (!isExcluded(name)) {
0419: filesIncluded.addElement(name);
0420: } else {
0421: filesExcluded.addElement(name);
0422: }
0423: } else {
0424: filesNotIncluded.addElement(name);
0425: }
0426: }
0427: }
0428:
0429: /**
0430: *
0431: * @param name path of the directory relative to the directory of
0432: * the fileset
0433: * @param file directory as file
0434: * @param fast
0435: */
0436: private void accountForIncludedDir(String name,
0437: AntFTPFile file, boolean fast) {
0438: if (!dirsIncluded.contains(name)
0439: && !dirsExcluded.contains(name)) {
0440:
0441: if (!isExcluded(name)) {
0442: if (fast) {
0443: if (file.isSymbolicLink()) {
0444: try {
0445: file.getClient()
0446: .changeWorkingDirectory(
0447: file.curpwd);
0448: } catch (IOException ioe) {
0449: throw new BuildException(
0450: "could not change directory to curpwd");
0451: }
0452: scandir(file.getLink(), name
0453: + File.separator, fast);
0454: } else {
0455: try {
0456: file.getClient()
0457: .changeWorkingDirectory(
0458: file.curpwd);
0459: } catch (IOException ioe) {
0460: throw new BuildException(
0461: "could not change directory to curpwd");
0462: }
0463: scandir(file.getName(), name
0464: + File.separator, fast);
0465: }
0466: }
0467: dirsIncluded.addElement(name);
0468: } else {
0469: dirsExcluded.addElement(name);
0470: if (fast && couldHoldIncluded(name)) {
0471: try {
0472: file.getClient().changeWorkingDirectory(
0473: file.curpwd);
0474: } catch (IOException ioe) {
0475: throw new BuildException(
0476: "could not change directory to curpwd");
0477: }
0478: scandir(file.getName(), name + File.separator,
0479: fast);
0480: }
0481: }
0482: }
0483: }
0484:
0485: /**
0486: * temporary table to speed up the various scanning methods below
0487: *
0488: * @since Ant 1.6
0489: */
0490: private Map fileListMap = new HashMap();
0491: /**
0492: * List of all scanned directories.
0493: *
0494: * @since Ant 1.6
0495: */
0496: private Set scannedDirs = new HashSet();
0497:
0498: /**
0499: * Has the directory with the given path relative to the base
0500: * directory already been scanned?
0501: *
0502: * <p>Registers the given directory as scanned as a side effect.</p>
0503: *
0504: * @since Ant 1.6
0505: */
0506: private boolean hasBeenScanned(String vpath) {
0507: return !scannedDirs.add(vpath);
0508: }
0509:
0510: /**
0511: * Clear internal caches.
0512: *
0513: * @since Ant 1.6
0514: */
0515: private void clearCaches() {
0516: fileListMap.clear();
0517: scannedDirs.clear();
0518: }
0519:
0520: /**
0521: * list the files present in one directory.
0522: * @param directory full path on the remote side
0523: * @param changedir if true change to directory directory before listing
0524: * @return array of FTPFile
0525: */
0526: public FTPFile[] listFiles(String directory, boolean changedir) {
0527: //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
0528: String currentPath = directory;
0529: if (changedir) {
0530: try {
0531: boolean result = ftp
0532: .changeWorkingDirectory(directory);
0533: if (!result) {
0534: return null;
0535: }
0536: currentPath = ftp.printWorkingDirectory();
0537: } catch (IOException ioe) {
0538: throw new BuildException(ioe, getLocation());
0539: }
0540: }
0541: if (fileListMap.containsKey(currentPath)) {
0542: getProject().log("filelist map used in listing files",
0543: Project.MSG_DEBUG);
0544: return ((FTPFile[]) fileListMap.get(currentPath));
0545: }
0546: FTPFile[] result = null;
0547: try {
0548: result = ftp.listFiles();
0549: } catch (IOException ioe) {
0550: throw new BuildException(ioe, getLocation());
0551: }
0552: fileListMap.put(currentPath, result);
0553: if (!remoteSensitivityChecked) {
0554: checkRemoteSensitivity(result, directory);
0555: }
0556: return result;
0557: }
0558:
0559: private void forceRemoteSensitivityCheck() {
0560: if (!remoteSensitivityChecked) {
0561: try {
0562: checkRemoteSensitivity(ftp.listFiles(), ftp
0563: .printWorkingDirectory());
0564: } catch (IOException ioe) {
0565: throw new BuildException(ioe, getLocation());
0566: }
0567: }
0568: }
0569:
0570: /**
0571: * cd into one directory and
0572: * list the files present in one directory.
0573: * @param directory full path on the remote side
0574: * @return array of FTPFile
0575: */
0576: public FTPFile[] listFiles(String directory) {
0577: return listFiles(directory, true);
0578: }
0579:
0580: private void checkRemoteSensitivity(FTPFile[] array,
0581: String directory) {
0582: if (array == null) {
0583: return;
0584: }
0585: boolean candidateFound = false;
0586: String target = null;
0587: for (int icounter = 0; icounter < array.length; icounter++) {
0588: if (array[icounter].isDirectory()) {
0589: if (!array[icounter].getName().equals(".")
0590: && !array[icounter].getName().equals("..")) {
0591: candidateFound = true;
0592: target = fiddleName(array[icounter].getName());
0593: getProject().log(
0594: "will try to cd to " + target
0595: + " where a directory called "
0596: + array[icounter].getName()
0597: + " exists", Project.MSG_DEBUG);
0598: for (int pcounter = 0; pcounter < array.length; pcounter++) {
0599: if (array[pcounter].getName()
0600: .equals(target)
0601: && pcounter != icounter) {
0602: candidateFound = false;
0603: }
0604: }
0605: if (candidateFound) {
0606: break;
0607: }
0608: }
0609: }
0610: }
0611: if (candidateFound) {
0612: try {
0613: getProject().log(
0614: "testing case sensitivity, attempting to cd to "
0615: + target, Project.MSG_DEBUG);
0616: remoteSystemCaseSensitive = !ftp
0617: .changeWorkingDirectory(target);
0618: } catch (IOException ioe) {
0619: remoteSystemCaseSensitive = true;
0620: } finally {
0621: try {
0622: ftp.changeWorkingDirectory(directory);
0623: } catch (IOException ioe) {
0624: throw new BuildException(ioe, getLocation());
0625: }
0626: }
0627: getProject().log(
0628: "remote system is case sensitive : "
0629: + remoteSystemCaseSensitive,
0630: Project.MSG_VERBOSE);
0631: remoteSensitivityChecked = true;
0632: }
0633: }
0634:
0635: private String fiddleName(String origin) {
0636: StringBuffer result = new StringBuffer();
0637: for (int icounter = 0; icounter < origin.length(); icounter++) {
0638: if (Character.isLowerCase(origin.charAt(icounter))) {
0639: result.append(Character.toUpperCase(origin
0640: .charAt(icounter)));
0641: } else if (Character.isUpperCase(origin
0642: .charAt(icounter))) {
0643: result.append(Character.toLowerCase(origin
0644: .charAt(icounter)));
0645: } else {
0646: result.append(origin.charAt(icounter));
0647: }
0648: }
0649: return result.toString();
0650: }
0651:
0652: /**
0653: * an AntFTPFile is a representation of a remote file
0654: * @since Ant 1.6
0655: */
0656: protected class AntFTPFile {
0657: /**
0658: * ftp client
0659: */
0660: private FTPClient client;
0661: /**
0662: * parent directory of the file
0663: */
0664: private String curpwd;
0665: /**
0666: * the file itself
0667: */
0668: private FTPFile ftpFile;
0669: /**
0670: *
0671: */
0672: private AntFTPFile parent = null;
0673: private boolean relativePathCalculated = false;
0674: private boolean traversesSymlinks = false;
0675: private String relativePath = "";
0676:
0677: /**
0678: * constructor
0679: * @param client ftp client variable
0680: * @param ftpFile the file
0681: * @param curpwd absolute remote path where the file is found
0682: */
0683: public AntFTPFile(FTPClient client, FTPFile ftpFile,
0684: String curpwd) {
0685: this .client = client;
0686: this .ftpFile = ftpFile;
0687: this .curpwd = curpwd;
0688: }
0689:
0690: /**
0691: * other constructor
0692: * @param parent the parent file
0693: * @param path a relative path to the parent file
0694: */
0695: public AntFTPFile(AntFTPFile parent, String path) {
0696: this .parent = parent;
0697: this .client = parent.client;
0698: Vector pathElements = SelectorUtils.tokenizePath(path);
0699: try {
0700: boolean result = this .client
0701: .changeWorkingDirectory(parent
0702: .getAbsolutePath());
0703: //this should not happen, except if parent has been deleted by another process
0704: if (!result) {
0705: return;
0706: }
0707: this .curpwd = parent.getAbsolutePath();
0708: } catch (IOException ioe) {
0709: throw new BuildException(
0710: "could not change working dir to "
0711: + parent.curpwd);
0712: }
0713: for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) {
0714: String currentPathElement = (String) pathElements
0715: .elementAt(fcount);
0716: try {
0717: boolean result = this .client
0718: .changeWorkingDirectory(currentPathElement);
0719: if (!result
0720: && !isCaseSensitive()
0721: && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
0722: currentPathElement = findPathElementCaseUnsensitive(
0723: this .curpwd, currentPathElement);
0724: if (currentPathElement == null) {
0725: return;
0726: }
0727: } else if (!result) {
0728: return;
0729: }
0730: this .curpwd = this .curpwd + remoteFileSep
0731: + currentPathElement;
0732: } catch (IOException ioe) {
0733: throw new BuildException(
0734: "could not change working dir to "
0735: + (String) pathElements
0736: .elementAt(fcount)
0737: + " from " + this .curpwd);
0738: }
0739:
0740: }
0741: String lastpathelement = (String) pathElements
0742: .elementAt(pathElements.size() - 1);
0743: FTPFile[] theFiles = listFiles(this .curpwd);
0744: this .ftpFile = getFile(theFiles, lastpathelement);
0745: }
0746:
0747: /**
0748: * find a file in a directory in case unsensitive way
0749: * @param parentPath where we are
0750: * @param soughtPathElement what is being sought
0751: * @return the first file found or null if not found
0752: */
0753: private String findPathElementCaseUnsensitive(
0754: String parentPath, String soughtPathElement) {
0755: // we are already in the right path, so the second parameter
0756: // is false
0757: FTPFile[] theFiles = listFiles(parentPath, false);
0758: if (theFiles == null) {
0759: return null;
0760: }
0761: for (int icounter = 0; icounter < theFiles.length; icounter++) {
0762: if (theFiles[icounter].getName().equalsIgnoreCase(
0763: soughtPathElement)) {
0764: return theFiles[icounter].getName();
0765: }
0766: }
0767: return null;
0768: }
0769:
0770: /**
0771: * find out if the file exists
0772: * @return true if the file exists
0773: */
0774: public boolean exists() {
0775: return (ftpFile != null);
0776: }
0777:
0778: /**
0779: * if the file is a symbolic link, find out to what it is pointing
0780: * @return the target of the symbolic link
0781: */
0782: public String getLink() {
0783: return ftpFile.getLink();
0784: }
0785:
0786: /**
0787: * get the name of the file
0788: * @return the name of the file
0789: */
0790: public String getName() {
0791: return ftpFile.getName();
0792: }
0793:
0794: /**
0795: * find out the absolute path of the file
0796: * @return absolute path as string
0797: */
0798: public String getAbsolutePath() {
0799: return curpwd + remoteFileSep + ftpFile.getName();
0800: }
0801:
0802: /**
0803: * find out the relative path assuming that the path used to construct
0804: * this AntFTPFile was spelled properly with regards to case.
0805: * This is OK on a case sensitive system such as UNIX
0806: * @return relative path
0807: */
0808: public String getFastRelativePath() {
0809: String absPath = getAbsolutePath();
0810: if (absPath.indexOf(rootPath + remoteFileSep) == 0) {
0811: return absPath.substring(rootPath.length()
0812: + remoteFileSep.length());
0813: }
0814: return null;
0815: }
0816:
0817: /**
0818: * find out the relative path to the rootPath of the enclosing scanner.
0819: * this relative path is spelled exactly like on disk,
0820: * for instance if the AntFTPFile has been instantiated as ALPHA,
0821: * but the file is really called alpha, this method will return alpha.
0822: * If a symbolic link is encountered, it is followed, but the name of the link
0823: * rather than the name of the target is returned.
0824: * (ie does not behave like File.getCanonicalPath())
0825: * @return relative path, separated by remoteFileSep
0826: * @throws IOException if a change directory fails, ...
0827: * @throws BuildException if one of the components of the relative path cannot
0828: * be found.
0829: */
0830: public String getRelativePath() throws IOException,
0831: BuildException {
0832: if (!relativePathCalculated) {
0833: if (parent != null) {
0834: traversesSymlinks = parent.isTraverseSymlinks();
0835: relativePath = getRelativePath(parent
0836: .getAbsolutePath(), parent
0837: .getRelativePath());
0838: } else {
0839: relativePath = getRelativePath(rootPath, "");
0840: relativePathCalculated = true;
0841: }
0842: }
0843: return relativePath;
0844: }
0845:
0846: /**
0847: * get thge relative path of this file
0848: * @param currentPath base path
0849: * @param currentRelativePath relative path of the base path with regards to remote dir
0850: * @return relative path
0851: */
0852: private String getRelativePath(String currentPath,
0853: String currentRelativePath) {
0854: Vector pathElements = SelectorUtils.tokenizePath(
0855: getAbsolutePath(), remoteFileSep);
0856: Vector pathElements2 = SelectorUtils.tokenizePath(
0857: currentPath, remoteFileSep);
0858: String relPath = currentRelativePath;
0859: for (int pcount = pathElements2.size(); pcount < pathElements
0860: .size(); pcount++) {
0861: String currentElement = (String) pathElements
0862: .elementAt(pcount);
0863: FTPFile[] theFiles = listFiles(currentPath);
0864: FTPFile theFile = null;
0865: if (theFiles != null) {
0866: theFile = getFile(theFiles, currentElement);
0867: }
0868: if (!relPath.equals("")) {
0869: relPath = relPath + remoteFileSep;
0870: }
0871: if (theFile == null) {
0872: // hit a hidden file assume not a symlink
0873: relPath = relPath + currentElement;
0874: currentPath = currentPath + remoteFileSep
0875: + currentElement;
0876: log("Hidden file " + relPath
0877: + " assumed to not be a symlink.",
0878: Project.MSG_VERBOSE);
0879: } else {
0880: traversesSymlinks = traversesSymlinks
0881: || theFile.isSymbolicLink();
0882: relPath = relPath + theFile.getName();
0883: currentPath = currentPath + remoteFileSep
0884: + theFile.getName();
0885: }
0886: }
0887: return relPath;
0888: }
0889:
0890: /**
0891: * find a file matching a string in an array of FTPFile.
0892: * This method will find "alpha" when requested for "ALPHA"
0893: * if and only if the caseSensitive attribute is set to false.
0894: * When caseSensitive is set to true, only the exact match is returned.
0895: * @param theFiles array of files
0896: * @param lastpathelement the file name being sought
0897: * @return null if the file cannot be found, otherwise return the matching file.
0898: */
0899: public FTPFile getFile(FTPFile[] theFiles,
0900: String lastpathelement) {
0901: if (theFiles == null) {
0902: return null;
0903: }
0904: for (int fcount = 0; fcount < theFiles.length; fcount++) {
0905: if (theFiles[fcount].getName().equals(
0906: lastpathelement)) {
0907: return theFiles[fcount];
0908: } else if (!isCaseSensitive()
0909: && theFiles[fcount].getName()
0910: .equalsIgnoreCase(lastpathelement)) {
0911: return theFiles[fcount];
0912: }
0913: }
0914: return null;
0915: }
0916:
0917: /**
0918: * tell if a file is a directory.
0919: * note that it will return false for symbolic links pointing to directories.
0920: * @return <code>true</code> for directories
0921: */
0922: public boolean isDirectory() {
0923: return ftpFile.isDirectory();
0924: }
0925:
0926: /**
0927: * tell if a file is a symbolic link
0928: * @return <code>true</code> for symbolic links
0929: */
0930: public boolean isSymbolicLink() {
0931: return ftpFile.isSymbolicLink();
0932: }
0933:
0934: /**
0935: * return the attached FTP client object.
0936: * Warning : this instance is really shared with the enclosing class.
0937: * @return FTP client
0938: */
0939: protected FTPClient getClient() {
0940: return client;
0941: }
0942:
0943: /**
0944: * sets the current path of an AntFTPFile
0945: * @param curpwd the current path one wants to set
0946: */
0947: protected void setCurpwd(String curpwd) {
0948: this .curpwd = curpwd;
0949: }
0950:
0951: /**
0952: * returns the path of the directory containing the AntFTPFile.
0953: * of the full path of the file itself in case of AntFTPRootFile
0954: * @return parent directory of the AntFTPFile
0955: */
0956: public String getCurpwd() {
0957: return curpwd;
0958: }
0959:
0960: /**
0961: * find out if a symbolic link is encountered in the relative path of this file
0962: * from rootPath.
0963: * @return <code>true</code> if a symbolic link is encountered in the relative path.
0964: * @throws IOException if one of the change directory or directory listing operations
0965: * fails
0966: * @throws BuildException if a path component in the relative path cannot be found.
0967: */
0968: public boolean isTraverseSymlinks() throws IOException,
0969: BuildException {
0970: if (!relativePathCalculated) {
0971: // getRelativePath also finds about symlinks
0972: getRelativePath();
0973: }
0974: return traversesSymlinks;
0975: }
0976:
0977: /**
0978: * Get a string rep of this object.
0979: * @return a string containing the pwd and the file.
0980: */
0981: public String toString() {
0982: return "AntFtpFile: " + curpwd + "%" + ftpFile;
0983: }
0984: }
0985:
0986: /**
0987: * special class to represent the remote directory itself
0988: * @since Ant 1.6
0989: */
0990: protected class AntFTPRootFile extends AntFTPFile {
0991: private String remotedir;
0992:
0993: /**
0994: * constructor
0995: * @param aclient FTP client
0996: * @param remotedir remote directory
0997: */
0998: public AntFTPRootFile(FTPClient aclient, String remotedir) {
0999: super (aclient, null, remotedir);
1000: this .remotedir = remotedir;
1001: try {
1002: this .getClient().changeWorkingDirectory(
1003: this .remotedir);
1004: this .setCurpwd(this .getClient()
1005: .printWorkingDirectory());
1006: } catch (IOException ioe) {
1007: throw new BuildException(ioe, getLocation());
1008: }
1009: }
1010:
1011: /**
1012: * find the absolute path
1013: * @return absolute path
1014: */
1015: public String getAbsolutePath() {
1016: return this .getCurpwd();
1017: }
1018:
1019: /**
1020: * find out the relative path to root
1021: * @return empty string
1022: * @throws BuildException actually never
1023: * @throws IOException actually never
1024: */
1025: public String getRelativePath() throws BuildException,
1026: IOException {
1027: return "";
1028: }
1029: }
1030: }
1031:
1032: /**
1033: * check FTPFiles to check whether they function as directories too
1034: * the FTPFile API seem to make directory and symbolic links incompatible
1035: * we want to find out if we can cd to a symbolic link
1036: * @param dir the parent directory of the file to test
1037: * @param file the file to test
1038: * @return true if it is possible to cd to this directory
1039: * @since ant 1.6
1040: */
1041: private boolean isFunctioningAsDirectory(FTPClient ftp, String dir,
1042: FTPFile file) {
1043: boolean result = false;
1044: String currentWorkingDir = null;
1045: if (file.isDirectory()) {
1046: return true;
1047: } else if (file.isFile()) {
1048: return false;
1049: }
1050: try {
1051: currentWorkingDir = ftp.printWorkingDirectory();
1052: } catch (IOException ioe) {
1053: getProject().log(
1054: "could not find current working directory " + dir
1055: + " while checking a symlink",
1056: Project.MSG_DEBUG);
1057: }
1058: if (currentWorkingDir != null) {
1059: try {
1060: result = ftp.changeWorkingDirectory(file.getLink());
1061: } catch (IOException ioe) {
1062: getProject().log(
1063: "could not cd to " + file.getLink()
1064: + " while checking a symlink",
1065: Project.MSG_DEBUG);
1066: }
1067: if (result) {
1068: boolean comeback = false;
1069: try {
1070: comeback = ftp
1071: .changeWorkingDirectory(currentWorkingDir);
1072: } catch (IOException ioe) {
1073: getProject().log(
1074: "could not cd back to " + dir
1075: + " while checking a symlink",
1076: Project.MSG_ERR);
1077: } finally {
1078: if (!comeback) {
1079: throw new BuildException(
1080: "could not cd back to " + dir
1081: + " while checking a symlink");
1082: }
1083: }
1084: }
1085: }
1086: return result;
1087: }
1088:
1089: /**
1090: * check FTPFiles to check whether they function as directories too
1091: * the FTPFile API seem to make directory and symbolic links incompatible
1092: * we want to find out if we can cd to a symbolic link
1093: * @param dir the parent directory of the file to test
1094: * @param file the file to test
1095: * @return true if it is possible to cd to this directory
1096: * @since ant 1.6
1097: */
1098: private boolean isFunctioningAsFile(FTPClient ftp, String dir,
1099: FTPFile file) {
1100: if (file.isDirectory()) {
1101: return false;
1102: } else if (file.isFile()) {
1103: return true;
1104: }
1105: return !isFunctioningAsDirectory(ftp, dir, file);
1106: }
1107:
1108: /**
1109: * Sets the remote directory where files will be placed. This may be a
1110: * relative or absolute path, and must be in the path syntax expected by
1111: * the remote server. No correction of path syntax will be performed.
1112: *
1113: * @param dir the remote directory name.
1114: */
1115: public void setRemotedir(String dir) {
1116: this .remotedir = dir;
1117: }
1118:
1119: /**
1120: * Sets the FTP server to send files to.
1121: *
1122: * @param server the remote server name.
1123: */
1124: public void setServer(String server) {
1125: this .server = server;
1126: }
1127:
1128: /**
1129: * Sets the FTP port used by the remote server.
1130: *
1131: * @param port the port on which the remote server is listening.
1132: */
1133: public void setPort(int port) {
1134: this .port = port;
1135: }
1136:
1137: /**
1138: * Sets the login user id to use on the specified server.
1139: *
1140: * @param userid remote system userid.
1141: */
1142: public void setUserid(String userid) {
1143: this .userid = userid;
1144: }
1145:
1146: /**
1147: * Sets the login password for the given user id.
1148: *
1149: * @param password the password on the remote system.
1150: */
1151: public void setPassword(String password) {
1152: this .password = password;
1153: }
1154:
1155: /**
1156: * Sets the login account to use on the specified server.
1157: *
1158: * @param pAccount the account name on remote system
1159: * @since Ant 1.7
1160: */
1161: public void setAccount(String pAccount) {
1162: this .account = pAccount;
1163: }
1164:
1165: /**
1166: * If true, uses binary mode, otherwise text mode (default is binary).
1167: *
1168: * @param binary if true use binary mode in transfers.
1169: */
1170: public void setBinary(boolean binary) {
1171: this .binary = binary;
1172: }
1173:
1174: /**
1175: * Specifies whether to use passive mode. Set to true if you are behind a
1176: * firewall and cannot connect without it. Passive mode is disabled by
1177: * default.
1178: *
1179: * @param passive true is passive mode should be used.
1180: */
1181: public void setPassive(boolean passive) {
1182: this .passive = passive;
1183: }
1184:
1185: /**
1186: * Set to true to receive notification about each file as it is
1187: * transferred.
1188: *
1189: * @param verbose true if verbose notifications are required.
1190: */
1191: public void setVerbose(boolean verbose) {
1192: this .verbose = verbose;
1193: }
1194:
1195: /**
1196: * A synonym for <tt>depends</tt>. Set to true to transmit only new
1197: * or changed files.
1198: *
1199: * See the related attributes timediffmillis and timediffauto.
1200: *
1201: * @param newer if true only transfer newer files.
1202: */
1203: public void setNewer(boolean newer) {
1204: this .newerOnly = newer;
1205: }
1206:
1207: /**
1208: * number of milliseconds to add to the time on the remote machine
1209: * to get the time on the local machine.
1210: *
1211: * use in conjunction with <code>newer</code>
1212: *
1213: * @param timeDiffMillis number of milliseconds
1214: *
1215: * @since ant 1.6
1216: */
1217: public void setTimeDiffMillis(long timeDiffMillis) {
1218: this .timeDiffMillis = timeDiffMillis;
1219: }
1220:
1221: /**
1222: * "true" to find out automatically the time difference
1223: * between local and remote machine.
1224: *
1225: * This requires right to create
1226: * and delete a temporary file in the remote directory.
1227: *
1228: * @param timeDiffAuto true = find automatically the time diff
1229: *
1230: * @since ant 1.6
1231: */
1232: public void setTimeDiffAuto(boolean timeDiffAuto) {
1233: this .timeDiffAuto = timeDiffAuto;
1234: }
1235:
1236: /**
1237: * Set to true to preserve modification times for "gotten" files.
1238: *
1239: * @param preserveLastModified if true preserver modification times.
1240: */
1241: public void setPreserveLastModified(boolean preserveLastModified) {
1242: this .preserveLastModified = preserveLastModified;
1243: }
1244:
1245: /**
1246: * Set to true to transmit only files that are new or changed from their
1247: * remote counterparts. The default is to transmit all files.
1248: *
1249: * @param depends if true only transfer newer files.
1250: */
1251: public void setDepends(boolean depends) {
1252: this .newerOnly = depends;
1253: }
1254:
1255: /**
1256: * Sets the remote file separator character. This normally defaults to the
1257: * Unix standard forward slash, but can be manually overridden using this
1258: * call if the remote server requires some other separator. Only the first
1259: * character of the string is used.
1260: *
1261: * @param separator the file separator on the remote system.
1262: */
1263: public void setSeparator(String separator) {
1264: remoteFileSep = separator;
1265: }
1266:
1267: /**
1268: * Sets the file permission mode (Unix only) for files sent to the
1269: * server.
1270: *
1271: * @param theMode unix style file mode for the files sent to the remote
1272: * system.
1273: */
1274: public void setChmod(String theMode) {
1275: this .chmod = theMode;
1276: }
1277:
1278: /**
1279: * Sets the default mask for file creation on a unix server.
1280: *
1281: * @param theUmask unix style umask for files created on the remote server.
1282: */
1283: public void setUmask(String theUmask) {
1284: this .umask = theUmask;
1285: }
1286:
1287: /**
1288: * A set of files to upload or download
1289: *
1290: * @param set the set of files to be added to the list of files to be
1291: * transferred.
1292: */
1293: public void addFileset(FileSet set) {
1294: filesets.addElement(set);
1295: }
1296:
1297: /**
1298: * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1299: * "mkdir", "chmod", "list", and "site".
1300: *
1301: * @deprecated since 1.5.x.
1302: * setAction(String) is deprecated and is replaced with
1303: * setAction(FTP.Action) to make Ant's Introspection mechanism do the
1304: * work and also to encapsulate operations on the type in its own
1305: * class.
1306: * @ant.attribute ignore="true"
1307: *
1308: * @param action the FTP action to be performed.
1309: *
1310: * @throws BuildException if the action is not a valid action.
1311: */
1312: public void setAction(String action) throws BuildException {
1313: log("DEPRECATED - The setAction(String) method has been deprecated."
1314: + " Use setAction(FTP.Action) instead.");
1315:
1316: Action a = new Action();
1317:
1318: a.setValue(action);
1319: this .action = a.getAction();
1320: }
1321:
1322: /**
1323: * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
1324: * "mkdir", "chmod", "list", and "site".
1325: *
1326: * @param action the FTP action to be performed.
1327: *
1328: * @throws BuildException if the action is not a valid action.
1329: */
1330: public void setAction(Action action) throws BuildException {
1331: this .action = action.getAction();
1332: }
1333:
1334: /**
1335: * The output file for the "list" action. This attribute is ignored for
1336: * any other actions.
1337: *
1338: * @param listing file in which to store the listing.
1339: */
1340: public void setListing(File listing) {
1341: this .listing = listing;
1342: }
1343:
1344: /**
1345: * If true, enables unsuccessful file put, delete and get
1346: * operations to be skipped with a warning and the remainder
1347: * of the files still transferred.
1348: *
1349: * @param skipFailedTransfers true if failures in transfers are ignored.
1350: */
1351: public void setSkipFailedTransfers(boolean skipFailedTransfers) {
1352: this .skipFailedTransfers = skipFailedTransfers;
1353: }
1354:
1355: /**
1356: * set the flag to skip errors on directory creation.
1357: * (and maybe later other server specific errors)
1358: *
1359: * @param ignoreNoncriticalErrors true if non-critical errors should not
1360: * cause a failure.
1361: */
1362: public void setIgnoreNoncriticalErrors(
1363: boolean ignoreNoncriticalErrors) {
1364: this .ignoreNoncriticalErrors = ignoreNoncriticalErrors;
1365: }
1366:
1367: private void configurationHasBeenSet() {
1368: this .isConfigurationSet = true;
1369: }
1370:
1371: /**
1372: * Sets the systemTypeKey attribute.
1373: * Method for setting <code>FTPClientConfig</code> remote system key.
1374: *
1375: * @param systemKey the key to be set - BUT if blank
1376: * the default value of null (which signifies "autodetect") will be kept.
1377: * @see org.apache.commons.net.ftp.FTPClientConfig
1378: */
1379: public void setSystemTypeKey(FTPSystemType systemKey) {
1380: if (systemKey != null && !systemKey.getValue().equals("")) {
1381: this .systemTypeKey = systemKey;
1382: configurationHasBeenSet();
1383: }
1384: }
1385:
1386: /**
1387: * Sets the defaultDateFormatConfig attribute.
1388: * @param defaultDateFormat configuration to be set, unless it is
1389: * null or empty string, in which case ignored.
1390: * @see org.apache.commons.net.ftp.FTPClientConfig
1391: */
1392: public void setDefaultDateFormatConfig(String defaultDateFormat) {
1393: if (defaultDateFormat != null && !defaultDateFormat.equals("")) {
1394: this .defaultDateFormatConfig = defaultDateFormat;
1395: configurationHasBeenSet();
1396: }
1397: }
1398:
1399: /**
1400: * Sets the recentDateFormatConfig attribute.
1401: * @param recentDateFormat configuration to be set, unless it is
1402: * null or empty string, in which case ignored.
1403: * @see org.apache.commons.net.ftp.FTPClientConfig
1404: */
1405: public void setRecentDateFormatConfig(String recentDateFormat) {
1406: if (recentDateFormat != null && !recentDateFormat.equals("")) {
1407: this .recentDateFormatConfig = recentDateFormat;
1408: configurationHasBeenSet();
1409: }
1410: }
1411:
1412: /**
1413: * Sets the serverLanguageCode attribute.
1414: * @param serverLanguageCode configuration to be set, unless it is
1415: * null or empty string, in which case ignored.
1416: * @see org.apache.commons.net.ftp.FTPClientConfig
1417: */
1418: public void setServerLanguageCodeConfig(
1419: LanguageCode serverLanguageCode) {
1420: if (serverLanguageCode != null
1421: && !serverLanguageCode.equals("")) {
1422: this .serverLanguageCodeConfig = serverLanguageCode;
1423: configurationHasBeenSet();
1424: }
1425: }
1426:
1427: /**
1428: * Sets the serverTimeZoneConfig attribute.
1429: * @param serverTimeZoneId configuration to be set, unless it is
1430: * null or empty string, in which case ignored.
1431: * @see org.apache.commons.net.ftp.FTPClientConfig
1432: */
1433: public void setServerTimeZoneConfig(String serverTimeZoneId) {
1434: if (serverTimeZoneId != null && !serverTimeZoneId.equals("")) {
1435: this .serverTimeZoneConfig = serverTimeZoneId;
1436: configurationHasBeenSet();
1437: }
1438: }
1439:
1440: /**
1441: * Sets the shortMonthNamesConfig attribute
1442: *
1443: * @param shortMonthNames configuration to be set, unless it is
1444: * null or empty string, in which case ignored.
1445: * @see org.apache.commons.net.ftp.FTPClientConfig
1446: */
1447: public void setShortMonthNamesConfig(String shortMonthNames) {
1448: if (shortMonthNames != null && !shortMonthNames.equals("")) {
1449: this .shortMonthNamesConfig = shortMonthNames;
1450: configurationHasBeenSet();
1451: }
1452: }
1453:
1454: /**
1455: * Defines how many times to retry executing FTP command before giving up.
1456: * Default is 0 - try once and if failure then give up.
1457: *
1458: * @param retriesAllowed number of retries to allow. -1 means
1459: * keep trying forever. "forever" may also be specified as a
1460: * synonym for -1.
1461: */
1462: public void setRetriesAllowed(String retriesAllowed) {
1463: if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
1464: this .retriesAllowed = Retryable.RETRY_FOREVER;
1465: } else {
1466: try {
1467: int retries = Integer.parseInt(retriesAllowed);
1468: if (retries < Retryable.RETRY_FOREVER) {
1469: throw new BuildException(
1470: "Invalid value for retriesAllowed attribute: "
1471: + retriesAllowed);
1472:
1473: }
1474: this .retriesAllowed = retries;
1475: } catch (NumberFormatException px) {
1476: throw new BuildException(
1477: "Invalid value for retriesAllowed attribute: "
1478: + retriesAllowed);
1479:
1480: }
1481:
1482: }
1483: }
1484:
1485: /**
1486: * @return Returns the systemTypeKey.
1487: */
1488: String getSystemTypeKey() {
1489: return systemTypeKey.getValue();
1490: }
1491:
1492: /**
1493: * @return Returns the defaultDateFormatConfig.
1494: */
1495: String getDefaultDateFormatConfig() {
1496: return defaultDateFormatConfig;
1497: }
1498:
1499: /**
1500: * @return Returns the recentDateFormatConfig.
1501: */
1502: String getRecentDateFormatConfig() {
1503: return recentDateFormatConfig;
1504: }
1505:
1506: /**
1507: * @return Returns the serverLanguageCodeConfig.
1508: */
1509: String getServerLanguageCodeConfig() {
1510: return serverLanguageCodeConfig.getValue();
1511: }
1512:
1513: /**
1514: * @return Returns the serverTimeZoneConfig.
1515: */
1516: String getServerTimeZoneConfig() {
1517: return serverTimeZoneConfig;
1518: }
1519:
1520: /**
1521: * @return Returns the shortMonthNamesConfig.
1522: */
1523: String getShortMonthNamesConfig() {
1524: return shortMonthNamesConfig;
1525: }
1526:
1527: /**
1528: * @return Returns the timestampGranularity.
1529: */
1530: Granularity getTimestampGranularity() {
1531: return timestampGranularity;
1532: }
1533:
1534: /**
1535: * Sets the timestampGranularity attribute
1536: * @param timestampGranularity The timestampGranularity to set.
1537: */
1538: public void setTimestampGranularity(Granularity timestampGranularity) {
1539: if (null == timestampGranularity
1540: || "".equals(timestampGranularity)) {
1541: return;
1542: }
1543: this .timestampGranularity = timestampGranularity;
1544: }
1545:
1546: /**
1547: * Sets the siteCommand attribute. This attribute
1548: * names the command that will be executed if the action
1549: * is "site".
1550: * @param siteCommand The siteCommand to set.
1551: */
1552: public void setSiteCommand(String siteCommand) {
1553: this .siteCommand = siteCommand;
1554: }
1555:
1556: /**
1557: * Sets the initialSiteCommand attribute. This attribute
1558: * names a site command that will be executed immediately
1559: * after connection.
1560: * @param initialCommand The initialSiteCommand to set.
1561: */
1562: public void setInitialSiteCommand(String initialCommand) {
1563: this .initialSiteCommand = initialCommand;
1564: }
1565:
1566: /**
1567: * Checks to see that all required parameters are set.
1568: *
1569: * @throws BuildException if the configuration is not valid.
1570: */
1571: protected void checkAttributes() throws BuildException {
1572: if (server == null) {
1573: throw new BuildException("server attribute must be set!");
1574: }
1575: if (userid == null) {
1576: throw new BuildException("userid attribute must be set!");
1577: }
1578: if (password == null) {
1579: throw new BuildException("password attribute must be set!");
1580: }
1581:
1582: if ((action == LIST_FILES) && (listing == null)) {
1583: throw new BuildException(
1584: "listing attribute must be set for list "
1585: + "action!");
1586: }
1587:
1588: if (action == MK_DIR && remotedir == null) {
1589: throw new BuildException(
1590: "remotedir attribute must be set for "
1591: + "mkdir action!");
1592: }
1593:
1594: if (action == CHMOD && chmod == null) {
1595: throw new BuildException(
1596: "chmod attribute must be set for chmod "
1597: + "action!");
1598: }
1599: if (action == SITE_CMD && siteCommand == null) {
1600: throw new BuildException(
1601: "sitecommand attribute must be set for site "
1602: + "action!");
1603: }
1604:
1605: if (this .isConfigurationSet) {
1606: try {
1607: Class
1608: .forName("org.apache.commons.net.ftp.FTPClientConfig");
1609: } catch (ClassNotFoundException e) {
1610: throw new BuildException(
1611: "commons-net.jar >= 1.4.0 is required for at least one"
1612: + " of the attributes specified.");
1613: }
1614: }
1615: }
1616:
1617: /**
1618: * Executable a retryable object.
1619: * @param h the retry hander.
1620: * @param r the object that should be retried until it succeeds
1621: * or the number of retrys is reached.
1622: * @param descr a description of the command that is being run.
1623: * @throws IOException if there is a problem.
1624: */
1625: protected void executeRetryable(RetryHandler h, Retryable r,
1626: String descr) throws IOException {
1627: h.execute(r, descr);
1628: }
1629:
1630: /**
1631: * For each file in the fileset, do the appropriate action: send, get,
1632: * delete, or list.
1633: *
1634: * @param ftp the FTPClient instance used to perform FTP actions
1635: * @param fs the fileset on which the actions are performed.
1636: *
1637: * @return the number of files to be transferred.
1638: *
1639: * @throws IOException if there is a problem reading a file
1640: * @throws BuildException if there is a problem in the configuration.
1641: */
1642: protected int transferFiles(final FTPClient ftp, FileSet fs)
1643: throws IOException, BuildException {
1644: DirectoryScanner ds;
1645: if (action == SEND_FILES) {
1646: ds = fs.getDirectoryScanner(getProject());
1647: } else {
1648: // warn that selectors are not supported
1649: if (fs.getSelectors(getProject()).length != 0) {
1650: getProject()
1651: .log(
1652: "selectors are not supported in remote filesets",
1653: Project.MSG_WARN);
1654: }
1655: ds = new FTPDirectoryScanner(ftp);
1656: fs.setupDirectoryScanner(ds, getProject());
1657: ds.setFollowSymlinks(fs.isFollowSymlinks());
1658: ds.scan();
1659: }
1660:
1661: String[] dsfiles = null;
1662: if (action == RM_DIR) {
1663: dsfiles = ds.getIncludedDirectories();
1664: } else {
1665: dsfiles = ds.getIncludedFiles();
1666: }
1667: String dir = null;
1668:
1669: if ((ds.getBasedir() == null)
1670: && ((action == SEND_FILES) || (action == GET_FILES))) {
1671: throw new BuildException(
1672: "the dir attribute must be set for send "
1673: + "and get actions");
1674: } else {
1675: if ((action == SEND_FILES) || (action == GET_FILES)) {
1676: dir = ds.getBasedir().getAbsolutePath();
1677: }
1678: }
1679:
1680: // If we are doing a listing, we need the output stream created now.
1681: BufferedWriter bw = null;
1682:
1683: try {
1684: if (action == LIST_FILES) {
1685: File pd = listing.getParentFile();
1686:
1687: if (!pd.exists()) {
1688: pd.mkdirs();
1689: }
1690: bw = new BufferedWriter(new FileWriter(listing));
1691: }
1692: RetryHandler h = new RetryHandler(this .retriesAllowed, this );
1693: if (action == RM_DIR) {
1694: // to remove directories, start by the end of the list
1695: // the trunk does not let itself be removed before the leaves
1696: for (int i = dsfiles.length - 1; i >= 0; i--) {
1697: final String dsfile = dsfiles[i];
1698: executeRetryable(h, new Retryable() {
1699: public void execute() throws IOException {
1700: rmDir(ftp, dsfile);
1701: }
1702: }, dsfile);
1703: }
1704: } else {
1705: final BufferedWriter fbw = bw;
1706: final String fdir = dir;
1707: if (this .newerOnly) {
1708: this .granularityMillis = this .timestampGranularity
1709: .getMilliseconds(action);
1710: }
1711: for (int i = 0; i < dsfiles.length; i++) {
1712: final String dsfile = dsfiles[i];
1713: executeRetryable(h, new Retryable() {
1714: public void execute() throws IOException {
1715: switch (action) {
1716: case SEND_FILES:
1717: sendFile(ftp, fdir, dsfile);
1718: break;
1719: case GET_FILES:
1720: getFile(ftp, fdir, dsfile);
1721: break;
1722: case DEL_FILES:
1723: delFile(ftp, dsfile);
1724: break;
1725: case LIST_FILES:
1726: listFile(ftp, fbw, dsfile);
1727: break;
1728: case CHMOD:
1729: doSiteCommand(ftp, "chmod " + chmod
1730: + " " + resolveFile(dsfile));
1731: transferred++;
1732: break;
1733: default:
1734: throw new BuildException(
1735: "unknown ftp action " + action);
1736: }
1737: }
1738: }, dsfile);
1739: }
1740: }
1741: } finally {
1742: if (bw != null) {
1743: bw.close();
1744: }
1745: }
1746:
1747: return dsfiles.length;
1748: }
1749:
1750: /**
1751: * Sends all files specified by the configured filesets to the remote
1752: * server.
1753: *
1754: * @param ftp the FTPClient instance used to perform FTP actions
1755: *
1756: * @throws IOException if there is a problem reading a file
1757: * @throws BuildException if there is a problem in the configuration.
1758: */
1759: protected void transferFiles(FTPClient ftp) throws IOException,
1760: BuildException {
1761: transferred = 0;
1762: skipped = 0;
1763:
1764: if (filesets.size() == 0) {
1765: throw new BuildException(
1766: "at least one fileset must be specified.");
1767: } else {
1768: // get files from filesets
1769: for (int i = 0; i < filesets.size(); i++) {
1770: FileSet fs = (FileSet) filesets.elementAt(i);
1771:
1772: if (fs != null) {
1773: transferFiles(ftp, fs);
1774: }
1775: }
1776: }
1777:
1778: log(transferred + " " + ACTION_TARGET_STRS[action] + " "
1779: + COMPLETED_ACTION_STRS[action]);
1780: if (skipped != 0) {
1781: log(skipped + " " + ACTION_TARGET_STRS[action]
1782: + " were not successfully "
1783: + COMPLETED_ACTION_STRS[action]);
1784: }
1785: }
1786:
1787: /**
1788: * Correct a file path to correspond to the remote host requirements. This
1789: * implementation currently assumes that the remote end can handle
1790: * Unix-style paths with forward-slash separators. This can be overridden
1791: * with the <code>separator</code> task parameter. No attempt is made to
1792: * determine what syntax is appropriate for the remote host.
1793: *
1794: * @param file the remote file name to be resolved
1795: *
1796: * @return the filename as it will appear on the server.
1797: */
1798: protected String resolveFile(String file) {
1799: return file.replace(System.getProperty("file.separator")
1800: .charAt(0), remoteFileSep.charAt(0));
1801: }
1802:
1803: /**
1804: * Creates all parent directories specified in a complete relative
1805: * pathname. Attempts to create existing directories will not cause
1806: * errors.
1807: *
1808: * @param ftp the FTP client instance to use to execute FTP actions on
1809: * the remote server.
1810: * @param filename the name of the file whose parents should be created.
1811: * @throws IOException under non documented circumstances
1812: * @throws BuildException if it is impossible to cd to a remote directory
1813: *
1814: */
1815: protected void createParents(FTPClient ftp, String filename)
1816: throws IOException, BuildException {
1817:
1818: File dir = new File(filename);
1819: if (dirCache.contains(dir)) {
1820: return;
1821: }
1822:
1823: Vector parents = new Vector();
1824: String dirname;
1825:
1826: while ((dirname = dir.getParent()) != null) {
1827: File checkDir = new File(dirname);
1828: if (dirCache.contains(checkDir)) {
1829: break;
1830: }
1831: dir = checkDir;
1832: parents.addElement(dir);
1833: }
1834:
1835: // find first non cached dir
1836: int i = parents.size() - 1;
1837:
1838: if (i >= 0) {
1839: String cwd = ftp.printWorkingDirectory();
1840: String parent = dir.getParent();
1841: if (parent != null) {
1842: if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
1843: throw new BuildException("could not change to "
1844: + "directory: " + ftp.getReplyString());
1845: }
1846: }
1847:
1848: while (i >= 0) {
1849: dir = (File) parents.elementAt(i--);
1850: // check if dir exists by trying to change into it.
1851: if (!ftp.changeWorkingDirectory(dir.getName())) {
1852: // could not change to it - try to create it
1853: log("creating remote directory "
1854: + resolveFile(dir.getPath()),
1855: Project.MSG_VERBOSE);
1856: if (!ftp.makeDirectory(dir.getName())) {
1857: handleMkDirFailure(ftp);
1858: }
1859: if (!ftp.changeWorkingDirectory(dir.getName())) {
1860: throw new BuildException("could not change to "
1861: + "directory: " + ftp.getReplyString());
1862: }
1863: }
1864: dirCache.addElement(dir);
1865: }
1866: ftp.changeWorkingDirectory(cwd);
1867: }
1868: }
1869:
1870: /**
1871: * auto find the time difference between local and remote
1872: * @param ftp handle to ftp client
1873: * @return number of millis to add to remote time to make it comparable to local time
1874: * @since ant 1.6
1875: */
1876: private long getTimeDiff(FTPClient ftp) {
1877: long returnValue = 0;
1878: File tempFile = findFileName(ftp);
1879: try {
1880: // create a local temporary file
1881: FILE_UTILS.createNewFile(tempFile);
1882: long localTimeStamp = tempFile.lastModified();
1883: BufferedInputStream instream = new BufferedInputStream(
1884: new FileInputStream(tempFile));
1885: ftp.storeFile(tempFile.getName(), instream);
1886: instream.close();
1887: boolean success = FTPReply.isPositiveCompletion(ftp
1888: .getReplyCode());
1889: if (success) {
1890: FTPFile[] ftpFiles = ftp.listFiles(tempFile.getName());
1891: if (ftpFiles.length == 1) {
1892: long remoteTimeStamp = ftpFiles[0].getTimestamp()
1893: .getTime().getTime();
1894: returnValue = localTimeStamp - remoteTimeStamp;
1895: }
1896: ftp.deleteFile(ftpFiles[0].getName());
1897: }
1898: // delegate the deletion of the local temp file to the delete task
1899: // because of race conditions occuring on Windows
1900: Delete mydelete = new Delete();
1901: mydelete.bindToOwner(this );
1902: mydelete.setFile(tempFile.getCanonicalFile());
1903: mydelete.execute();
1904: } catch (Exception e) {
1905: throw new BuildException(e, getLocation());
1906: }
1907: return returnValue;
1908: }
1909:
1910: /**
1911: * find a suitable name for local and remote temporary file
1912: */
1913: private File findFileName(FTPClient ftp) {
1914: FTPFile[] theFiles = null;
1915: final int maxIterations = 1000;
1916: for (int counter = 1; counter < maxIterations; counter++) {
1917: File localFile = FILE_UTILS.createTempFile("ant"
1918: + Integer.toString(counter), ".tmp", null);
1919: String fileName = localFile.getName();
1920: boolean found = false;
1921: try {
1922: if (counter == 1) {
1923: theFiles = ftp.listFiles();
1924: }
1925: for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
1926: if (theFiles[counter2].getName().equals(fileName)) {
1927: found = true;
1928: break;
1929: }
1930: }
1931: } catch (IOException ioe) {
1932: throw new BuildException(ioe, getLocation());
1933: }
1934: if (!found) {
1935: localFile.deleteOnExit();
1936: return localFile;
1937: }
1938: }
1939: return null;
1940: }
1941:
1942: private static final SimpleDateFormat TIMESTAMP_LOGGING_SDF = new SimpleDateFormat(
1943: "yyyy-MM-dd HH:mm:ss");
1944:
1945: /**
1946: * Checks to see if the remote file is current as compared with the local
1947: * file. Returns true if the target file is up to date.
1948: * @param ftp ftpclient
1949: * @param localFile local file
1950: * @param remoteFile remote file
1951: * @return true if the target file is up to date
1952: * @throws IOException in unknown circumstances
1953: * @throws BuildException if the date of the remote files cannot be found and the action is
1954: * GET_FILES
1955: */
1956: protected boolean isUpToDate(FTPClient ftp, File localFile,
1957: String remoteFile) throws IOException, BuildException {
1958: log("checking date for " + remoteFile, Project.MSG_VERBOSE);
1959:
1960: FTPFile[] files = ftp.listFiles(remoteFile);
1961:
1962: // For Microsoft's Ftp-Service an Array with length 0 is
1963: // returned if configured to return listings in "MS-DOS"-Format
1964: if (files == null || files.length == 0) {
1965: // If we are sending files, then assume out of date.
1966: // If we are getting files, then throw an error
1967:
1968: if (action == SEND_FILES) {
1969: log("Could not date test remote file: " + remoteFile
1970: + "assuming out of date.", Project.MSG_VERBOSE);
1971: return false;
1972: } else {
1973: throw new BuildException(
1974: "could not date test remote file: "
1975: + ftp.getReplyString());
1976: }
1977: }
1978:
1979: long remoteTimestamp = files[0].getTimestamp().getTime()
1980: .getTime();
1981: long localTimestamp = localFile.lastModified();
1982: long adjustedRemoteTimestamp = remoteTimestamp
1983: + this .timeDiffMillis + this .granularityMillis;
1984:
1985: StringBuffer msg = new StringBuffer(" [").append(
1986: TIMESTAMP_LOGGING_SDF.format(new Date(localTimestamp)))
1987: .append("] local");
1988: log(msg.toString(), Project.MSG_VERBOSE);
1989:
1990: msg = new StringBuffer(" [").append(
1991: TIMESTAMP_LOGGING_SDF.format(new Date(
1992: adjustedRemoteTimestamp))).append("] remote");
1993: if (remoteTimestamp != adjustedRemoteTimestamp) {
1994: msg.append(" - (raw: ").append(
1995: TIMESTAMP_LOGGING_SDF.format(new Date(
1996: remoteTimestamp))).append(")");
1997: }
1998: log(msg.toString(), Project.MSG_VERBOSE);
1999:
2000: if (this .action == SEND_FILES) {
2001: return adjustedRemoteTimestamp >= localTimestamp;
2002: } else {
2003: return localTimestamp >= adjustedRemoteTimestamp;
2004: }
2005: }
2006:
2007: /**
2008: * Sends a site command to the ftp server
2009: * @param ftp ftp client
2010: * @param theCMD command to execute
2011: * @throws IOException in unknown circumstances
2012: * @throws BuildException in unknown circumstances
2013: */
2014: protected void doSiteCommand(FTPClient ftp, String theCMD)
2015: throws IOException, BuildException {
2016: boolean rc;
2017: String[] myReply = null;
2018:
2019: log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
2020:
2021: rc = ftp.sendSiteCommand(theCMD);
2022:
2023: if (!rc) {
2024: log("Failed to issue Site Command: " + theCMD,
2025: Project.MSG_WARN);
2026: } else {
2027:
2028: myReply = ftp.getReplyStrings();
2029:
2030: for (int x = 0; x < myReply.length; x++) {
2031: if (myReply[x].indexOf("200") == -1) {
2032: log(myReply[x], Project.MSG_WARN);
2033: }
2034: }
2035: }
2036: }
2037:
2038: /**
2039: * Sends a single file to the remote host. <code>filename</code> may
2040: * contain a relative path specification. When this is the case, <code>sendFile</code>
2041: * will attempt to create any necessary parent directories before sending
2042: * the file. The file will then be sent using the entire relative path
2043: * spec - no attempt is made to change directories. It is anticipated that
2044: * this may eventually cause problems with some FTP servers, but it
2045: * simplifies the coding.
2046: * @param ftp ftp client
2047: * @param dir base directory of the file to be sent (local)
2048: * @param filename relative path of the file to be send
2049: * locally relative to dir
2050: * remotely relative to the remotedir attribute
2051: * @throws IOException in unknown circumstances
2052: * @throws BuildException in unknown circumstances
2053: */
2054: protected void sendFile(FTPClient ftp, String dir, String filename)
2055: throws IOException, BuildException {
2056: InputStream instream = null;
2057:
2058: try {
2059: // XXX - why not simply new File(dir, filename)?
2060: File file = getProject().resolveFile(
2061: new File(dir, filename).getPath());
2062:
2063: if (newerOnly
2064: && isUpToDate(ftp, file, resolveFile(filename))) {
2065: return;
2066: }
2067:
2068: if (verbose) {
2069: log("transferring " + file.getAbsolutePath());
2070: }
2071:
2072: instream = new BufferedInputStream(
2073: new FileInputStream(file));
2074:
2075: createParents(ftp, filename);
2076:
2077: ftp.storeFile(resolveFile(filename), instream);
2078:
2079: boolean success = FTPReply.isPositiveCompletion(ftp
2080: .getReplyCode());
2081:
2082: if (!success) {
2083: String s = "could not put file: "
2084: + ftp.getReplyString();
2085:
2086: if (skipFailedTransfers) {
2087: log(s, Project.MSG_WARN);
2088: skipped++;
2089: } else {
2090: throw new BuildException(s);
2091: }
2092:
2093: } else {
2094: // see if we should issue a chmod command
2095: if (chmod != null) {
2096: doSiteCommand(ftp, "chmod " + chmod + " "
2097: + resolveFile(filename));
2098: }
2099: log("File " + file.getAbsolutePath() + " copied to "
2100: + server, Project.MSG_VERBOSE);
2101: transferred++;
2102: }
2103: } finally {
2104: if (instream != null) {
2105: try {
2106: instream.close();
2107: } catch (IOException ex) {
2108: // ignore it
2109: }
2110: }
2111: }
2112: }
2113:
2114: /**
2115: * Delete a file from the remote host.
2116: * @param ftp ftp client
2117: * @param filename file to delete
2118: * @throws IOException in unknown circumstances
2119: * @throws BuildException if skipFailedTransfers is set to false
2120: * and the deletion could not be done
2121: */
2122: protected void delFile(FTPClient ftp, String filename)
2123: throws IOException, BuildException {
2124: if (verbose) {
2125: log("deleting " + filename);
2126: }
2127:
2128: if (!ftp.deleteFile(resolveFile(filename))) {
2129: String s = "could not delete file: " + ftp.getReplyString();
2130:
2131: if (skipFailedTransfers) {
2132: log(s, Project.MSG_WARN);
2133: skipped++;
2134: } else {
2135: throw new BuildException(s);
2136: }
2137: } else {
2138: log("File " + filename + " deleted from " + server,
2139: Project.MSG_VERBOSE);
2140: transferred++;
2141: }
2142: }
2143:
2144: /**
2145: * Delete a directory, if empty, from the remote host.
2146: * @param ftp ftp client
2147: * @param dirname directory to delete
2148: * @throws IOException in unknown circumstances
2149: * @throws BuildException if skipFailedTransfers is set to false
2150: * and the deletion could not be done
2151: */
2152: protected void rmDir(FTPClient ftp, String dirname)
2153: throws IOException, BuildException {
2154: if (verbose) {
2155: log("removing " + dirname);
2156: }
2157:
2158: if (!ftp.removeDirectory(resolveFile(dirname))) {
2159: String s = "could not remove directory: "
2160: + ftp.getReplyString();
2161:
2162: if (skipFailedTransfers) {
2163: log(s, Project.MSG_WARN);
2164: skipped++;
2165: } else {
2166: throw new BuildException(s);
2167: }
2168: } else {
2169: log("Directory " + dirname + " removed from " + server,
2170: Project.MSG_VERBOSE);
2171: transferred++;
2172: }
2173: }
2174:
2175: /**
2176: * Retrieve a single file from the remote host. <code>filename</code> may
2177: * contain a relative path specification. <p>
2178: *
2179: * The file will then be retreived using the entire relative path spec -
2180: * no attempt is made to change directories. It is anticipated that this
2181: * may eventually cause problems with some FTP servers, but it simplifies
2182: * the coding.</p>
2183: * @param ftp the ftp client
2184: * @param dir local base directory to which the file should go back
2185: * @param filename relative path of the file based upon the ftp remote directory
2186: * and/or the local base directory (dir)
2187: * @throws IOException in unknown circumstances
2188: * @throws BuildException if skipFailedTransfers is false
2189: * and the file cannot be retrieved.
2190: */
2191: protected void getFile(FTPClient ftp, String dir, String filename)
2192: throws IOException, BuildException {
2193: OutputStream outstream = null;
2194: try {
2195: File file = getProject().resolveFile(
2196: new File(dir, filename).getPath());
2197:
2198: if (newerOnly
2199: && isUpToDate(ftp, file, resolveFile(filename))) {
2200: return;
2201: }
2202:
2203: if (verbose) {
2204: log("transferring " + filename + " to "
2205: + file.getAbsolutePath());
2206: }
2207:
2208: File pdir = file.getParentFile();
2209:
2210: if (!pdir.exists()) {
2211: pdir.mkdirs();
2212: }
2213: outstream = new BufferedOutputStream(new FileOutputStream(
2214: file));
2215: ftp.retrieveFile(resolveFile(filename), outstream);
2216:
2217: if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2218: String s = "could not get file: "
2219: + ftp.getReplyString();
2220:
2221: if (skipFailedTransfers) {
2222: log(s, Project.MSG_WARN);
2223: skipped++;
2224: } else {
2225: throw new BuildException(s);
2226: }
2227:
2228: } else {
2229: log("File " + file.getAbsolutePath() + " copied from "
2230: + server, Project.MSG_VERBOSE);
2231: transferred++;
2232: if (preserveLastModified) {
2233: outstream.close();
2234: outstream = null;
2235: FTPFile[] remote = ftp
2236: .listFiles(resolveFile(filename));
2237: if (remote.length > 0) {
2238: FILE_UTILS.setFileLastModified(file, remote[0]
2239: .getTimestamp().getTime().getTime());
2240: }
2241: }
2242: }
2243: } finally {
2244: if (outstream != null) {
2245: try {
2246: outstream.close();
2247: } catch (IOException ex) {
2248: // ignore it
2249: }
2250: }
2251: }
2252: }
2253:
2254: /**
2255: * List information about a single file from the remote host. <code>filename</code>
2256: * may contain a relative path specification. <p>
2257: *
2258: * The file listing will then be retrieved using the entire relative path
2259: * spec - no attempt is made to change directories. It is anticipated that
2260: * this may eventually cause problems with some FTP servers, but it
2261: * simplifies the coding.</p>
2262: * @param ftp ftp client
2263: * @param bw buffered writer
2264: * @param filename the directory one wants to list
2265: * @throws IOException in unknown circumstances
2266: * @throws BuildException in unknown circumstances
2267: */
2268: protected void listFile(FTPClient ftp, BufferedWriter bw,
2269: String filename) throws IOException, BuildException {
2270: if (verbose) {
2271: log("listing " + filename);
2272: }
2273: FTPFile[] ftpfiles = ftp.listFiles(resolveFile(filename));
2274:
2275: if (ftpfiles != null && ftpfiles.length > 0) {
2276: bw.write(ftpfiles[0].toString());
2277: bw.newLine();
2278: transferred++;
2279: }
2280: }
2281:
2282: /**
2283: * Create the specified directory on the remote host.
2284: *
2285: * @param ftp The FTP client connection
2286: * @param dir The directory to create (format must be correct for host
2287: * type)
2288: * @throws IOException in unknown circumstances
2289: * @throws BuildException if ignoreNoncriticalErrors has not been set to true
2290: * and a directory could not be created, for instance because it was
2291: * already existing. Precisely, the codes 521, 550 and 553 will trigger
2292: * a BuildException
2293: */
2294: protected void makeRemoteDir(FTPClient ftp, String dir)
2295: throws IOException, BuildException {
2296: String workingDirectory = ftp.printWorkingDirectory();
2297: if (verbose) {
2298: log("Creating directory: " + dir);
2299: }
2300: if (dir.indexOf("/") == 0) {
2301: ftp.changeWorkingDirectory("/");
2302: }
2303: String subdir = new String();
2304: StringTokenizer st = new StringTokenizer(dir, "/");
2305: while (st.hasMoreTokens()) {
2306: subdir = st.nextToken();
2307: log("Checking " + subdir, Project.MSG_DEBUG);
2308: if (!ftp.changeWorkingDirectory(subdir)) {
2309: if (!ftp.makeDirectory(subdir)) {
2310: // codes 521, 550 and 553 can be produced by FTP Servers
2311: // to indicate that an attempt to create a directory has
2312: // failed because the directory already exists.
2313: int rc = ftp.getReplyCode();
2314: if (!(ignoreNoncriticalErrors && (rc == FTPReply.CODE_550
2315: || rc == FTPReply.CODE_553 || rc == CODE_521))) {
2316: throw new BuildException(
2317: "could not create directory: "
2318: + ftp.getReplyString());
2319: }
2320: if (verbose) {
2321: log("Directory already exists");
2322: }
2323: } else {
2324: if (verbose) {
2325: log("Directory created OK");
2326: }
2327: ftp.changeWorkingDirectory(subdir);
2328: }
2329: }
2330: }
2331: if (workingDirectory != null) {
2332: ftp.changeWorkingDirectory(workingDirectory);
2333: }
2334: }
2335:
2336: /**
2337: * look at the response for a failed mkdir action, decide whether
2338: * it matters or not. If it does, we throw an exception
2339: * @param ftp current ftp connection
2340: * @throws BuildException if this is an error to signal
2341: */
2342: private void handleMkDirFailure(FTPClient ftp)
2343: throws BuildException {
2344: int rc = ftp.getReplyCode();
2345: if (!(ignoreNoncriticalErrors && (rc == FTPReply.CODE_550
2346: || rc == FTPReply.CODE_553 || rc == CODE_521))) {
2347: throw new BuildException("could not create directory: "
2348: + ftp.getReplyString());
2349: }
2350: }
2351:
2352: /**
2353: * Runs the task.
2354: *
2355: * @throws BuildException if the task fails or is not configured
2356: * correctly.
2357: */
2358: public void execute() throws BuildException {
2359: checkAttributes();
2360:
2361: FTPClient ftp = null;
2362:
2363: try {
2364: log("Opening FTP connection to " + server,
2365: Project.MSG_VERBOSE);
2366:
2367: ftp = new FTPClient();
2368: if (this .isConfigurationSet) {
2369: ftp = FTPConfigurator.configure(ftp, this );
2370: }
2371:
2372: ftp.connect(server, port);
2373: if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2374: throw new BuildException("FTP connection failed: "
2375: + ftp.getReplyString());
2376: }
2377:
2378: log("connected", Project.MSG_VERBOSE);
2379: log("logging in to FTP server", Project.MSG_VERBOSE);
2380:
2381: if ((this .account != null && !ftp.login(userid, password,
2382: account))
2383: || (this .account == null && !ftp.login(userid,
2384: password))) {
2385: throw new BuildException(
2386: "Could not login to FTP server");
2387: }
2388:
2389: log("login succeeded", Project.MSG_VERBOSE);
2390:
2391: if (binary) {
2392: ftp
2393: .setFileType(org.apache.commons.net.ftp.FTP.IMAGE_FILE_TYPE);
2394: if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2395: throw new BuildException(
2396: "could not set transfer type: "
2397: + ftp.getReplyString());
2398: }
2399: } else {
2400: ftp
2401: .setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
2402: if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2403: throw new BuildException(
2404: "could not set transfer type: "
2405: + ftp.getReplyString());
2406: }
2407: }
2408:
2409: if (passive) {
2410: log("entering passive mode", Project.MSG_VERBOSE);
2411: ftp.enterLocalPassiveMode();
2412: if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
2413: throw new BuildException(
2414: "could not enter into passive " + "mode: "
2415: + ftp.getReplyString());
2416: }
2417: }
2418:
2419: // If an initial command was configured then send it.
2420: // Some FTP servers offer different modes of operation,
2421: // E.G. switching between a UNIX file system mode and
2422: // a legacy file system.
2423: if (this .initialSiteCommand != null) {
2424: RetryHandler h = new RetryHandler(this .retriesAllowed,
2425: this );
2426: final FTPClient lftp = ftp;
2427: executeRetryable(h, new Retryable() {
2428: public void execute() throws IOException {
2429: doSiteCommand(lftp, FTP.this .initialSiteCommand);
2430: }
2431: }, "initial site command: " + this .initialSiteCommand);
2432: }
2433:
2434: // For a unix ftp server you can set the default mask for all files
2435: // created.
2436:
2437: if (umask != null) {
2438: RetryHandler h = new RetryHandler(this .retriesAllowed,
2439: this );
2440: final FTPClient lftp = ftp;
2441: executeRetryable(h, new Retryable() {
2442: public void execute() throws IOException {
2443: doSiteCommand(lftp, "umask " + umask);
2444: }
2445: }, "umask " + umask);
2446: }
2447:
2448: // If the action is MK_DIR, then the specified remote
2449: // directory is the directory to create.
2450:
2451: if (action == MK_DIR) {
2452: RetryHandler h = new RetryHandler(this .retriesAllowed,
2453: this );
2454: final FTPClient lftp = ftp;
2455: executeRetryable(h, new Retryable() {
2456: public void execute() throws IOException {
2457: makeRemoteDir(lftp, remotedir);
2458: }
2459: }, remotedir);
2460: } else if (action == SITE_CMD) {
2461: RetryHandler h = new RetryHandler(this .retriesAllowed,
2462: this );
2463: final FTPClient lftp = ftp;
2464: executeRetryable(h, new Retryable() {
2465: public void execute() throws IOException {
2466: doSiteCommand(lftp, FTP.this .siteCommand);
2467: }
2468: }, "Site Command: " + this .siteCommand);
2469: } else {
2470: if (remotedir != null) {
2471: log("changing the remote directory",
2472: Project.MSG_VERBOSE);
2473: ftp.changeWorkingDirectory(remotedir);
2474: if (!FTPReply.isPositiveCompletion(ftp
2475: .getReplyCode())) {
2476: throw new BuildException(
2477: "could not change remote "
2478: + "directory: "
2479: + ftp.getReplyString());
2480: }
2481: }
2482: if (newerOnly && timeDiffAuto) {
2483: // in this case we want to find how much time span there is between local
2484: // and remote
2485: timeDiffMillis = getTimeDiff(ftp);
2486: }
2487: log(ACTION_STRS[action] + " "
2488: + ACTION_TARGET_STRS[action]);
2489: transferFiles(ftp);
2490: }
2491:
2492: } catch (IOException ex) {
2493: throw new BuildException(
2494: "error during FTP transfer: " + ex, ex);
2495: } finally {
2496: if (ftp != null && ftp.isConnected()) {
2497: try {
2498: log("disconnecting", Project.MSG_VERBOSE);
2499: ftp.logout();
2500: ftp.disconnect();
2501: } catch (IOException ex) {
2502: // ignore it
2503: }
2504: }
2505: }
2506: }
2507:
2508: /**
2509: * an action to perform, one of
2510: * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
2511: * "rmdir"
2512: */
2513: public static class Action extends EnumeratedAttribute {
2514:
2515: private static final String[] VALID_ACTIONS = { "send", "put",
2516: "recv", "get", "del", "delete", "list", "mkdir",
2517: "chmod", "rmdir", "site" };
2518:
2519: /**
2520: * Get the valid values
2521: *
2522: * @return an array of the valid FTP actions.
2523: */
2524: public String[] getValues() {
2525: return VALID_ACTIONS;
2526: }
2527:
2528: /**
2529: * Get the symbolic equivalent of the action value.
2530: *
2531: * @return the SYMBOL representing the given action.
2532: */
2533: public int getAction() {
2534: String actionL = getValue().toLowerCase(Locale.US);
2535:
2536: if (actionL.equals("send") || actionL.equals("put")) {
2537: return SEND_FILES;
2538: } else if (actionL.equals("recv") || actionL.equals("get")) {
2539: return GET_FILES;
2540: } else if (actionL.equals("del")
2541: || actionL.equals("delete")) {
2542: return DEL_FILES;
2543: } else if (actionL.equals("list")) {
2544: return LIST_FILES;
2545: } else if (actionL.equals("chmod")) {
2546: return CHMOD;
2547: } else if (actionL.equals("mkdir")) {
2548: return MK_DIR;
2549: } else if (actionL.equals("rmdir")) {
2550: return RM_DIR;
2551: } else if (actionL.equals("site")) {
2552: return SITE_CMD;
2553: }
2554: return SEND_FILES;
2555: }
2556: }
2557:
2558: /**
2559: * represents one of the valid timestamp adjustment values
2560: * recognized by the <code>timestampGranularity</code> attribute.<p>
2561:
2562: * A timestamp adjustment may be used in file transfers for checking
2563: * uptodateness. MINUTE means to add one minute to the server
2564: * timestamp. This is done because FTP servers typically list
2565: * timestamps HH:mm and client FileSystems typically use HH:mm:ss.
2566: *
2567: * The default is to use MINUTE for PUT actions and NONE for GET
2568: * actions, since GETs have the <code>preserveLastModified</code>
2569: * option, which takes care of the problem in most use cases where
2570: * this level of granularity is an issue.
2571: *
2572: */
2573: public static class Granularity extends EnumeratedAttribute {
2574:
2575: private static final String[] VALID_GRANULARITIES = { "",
2576: "MINUTE", "NONE" };
2577:
2578: /**
2579: * Get the valid values.
2580: * @return the list of valid Granularity values
2581: */
2582: public String[] getValues() {
2583: // TODO Auto-generated method stub
2584: return VALID_GRANULARITIES;
2585: }
2586:
2587: /**
2588: * returns the number of milliseconds associated with
2589: * the attribute, which can vary in some cases depending
2590: * on the value of the action parameter.
2591: * @param action SEND_FILES or GET_FILES
2592: * @return the number of milliseconds associated with
2593: * the attribute, in the context of the supplied action
2594: */
2595: public long getMilliseconds(int action) {
2596: String granularityU = getValue().toUpperCase(Locale.US);
2597:
2598: if ("".equals(granularityU)) {
2599: if (action == SEND_FILES) {
2600: return GRANULARITY_MINUTE;
2601: }
2602: } else if ("MINUTE".equals(granularityU)) {
2603: return GRANULARITY_MINUTE;
2604: }
2605: return 0L;
2606: }
2607:
2608: static final Granularity getDefault() {
2609: Granularity g = new Granularity();
2610: g.setValue("");
2611: return g;
2612: }
2613:
2614: }
2615:
2616: /**
2617: * one of the valid system type keys recognized by the systemTypeKey
2618: * attribute.
2619: */
2620: public static class FTPSystemType extends EnumeratedAttribute {
2621:
2622: private static final String[] VALID_SYSTEM_TYPES = { "",
2623: "UNIX", "VMS", "WINDOWS", "OS/2", "OS/400", "MVS" };
2624:
2625: /**
2626: * Get the valid values.
2627: * @return the list of valid system types.
2628: */
2629: public String[] getValues() {
2630: return VALID_SYSTEM_TYPES;
2631: }
2632:
2633: static final FTPSystemType getDefault() {
2634: FTPSystemType ftpst = new FTPSystemType();
2635: ftpst.setValue("");
2636: return ftpst;
2637: }
2638: }
2639:
2640: /**
2641: * Enumerated class for languages.
2642: */
2643: public static class LanguageCode extends EnumeratedAttribute {
2644:
2645: private static final String[] VALID_LANGUAGE_CODES = getValidLanguageCodes();
2646:
2647: private static String[] getValidLanguageCodes() {
2648: Collection c = FTPClientConfig.getSupportedLanguageCodes();
2649: String[] ret = new String[c.size() + 1];
2650: int i = 0;
2651: ret[i++] = "";
2652: for (Iterator it = c.iterator(); it.hasNext(); i++) {
2653: ret[i] = (String) it.next();
2654: }
2655: return ret;
2656: }
2657:
2658: /**
2659: * Return the value values.
2660: * @return the list of valid language types.
2661: */
2662: public String[] getValues() {
2663: return VALID_LANGUAGE_CODES;
2664: }
2665:
2666: static final LanguageCode getDefault() {
2667: LanguageCode lc = new LanguageCode();
2668: lc.setValue("");
2669: return lc;
2670: }
2671: }
2672:
2673: }
|