1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.net;
  18. import org.apache.commons.net.ftp.FTPClient;
  19. import org.apache.commons.net.ftp.FTPFile;
  20. import org.apache.commons.net.ftp.FTPReply;
  21. import java.io.BufferedInputStream;
  22. import java.io.BufferedOutputStream;
  23. import java.io.BufferedWriter;
  24. import java.io.File;
  25. import java.io.FileInputStream;
  26. import java.io.FileOutputStream;
  27. import java.io.FileWriter;
  28. import java.io.IOException;
  29. import java.io.InputStream;
  30. import java.io.OutputStream;
  31. import java.util.Enumeration;
  32. import java.util.HashMap;
  33. import java.util.Hashtable;
  34. import java.util.HashSet;
  35. import java.util.Locale;
  36. import java.util.Map;
  37. import java.util.Set;
  38. import java.util.StringTokenizer;
  39. import java.util.Vector;
  40. import org.apache.tools.ant.BuildException;
  41. import org.apache.tools.ant.DirectoryScanner;
  42. import org.apache.tools.ant.Project;
  43. import org.apache.tools.ant.Task;
  44. import org.apache.tools.ant.taskdefs.Delete;
  45. import org.apache.tools.ant.types.EnumeratedAttribute;
  46. import org.apache.tools.ant.types.FileSet;
  47. import org.apache.tools.ant.types.selectors.SelectorUtils;
  48. import org.apache.tools.ant.util.FileUtils;
  49. /**
  50. * Basic FTP client. Performs the following actions:
  51. * <ul>
  52. * <li> <strong>send</strong> - send files to a remote server. This is the
  53. * default action.</li>
  54. * <li> <strong>get</strong> - retrieve files from a remote server.</li>
  55. * <li> <strong>del</strong> - delete files from a remote server.</li>
  56. * <li> <strong>list</strong> - create a file listing.</li>
  57. * <li> <strong>chmod</strong> - change unix file permissions.</li>
  58. * <li> <strong>rmdir</strong> - remove directories, if empty, from a
  59. * remote server.</li>
  60. * </ul>
  61. * <strong>Note:</strong> Some FTP servers - notably the Solaris server - seem
  62. * to hold data ports open after a "retr" operation, allowing them to timeout
  63. * instead of shutting them down cleanly. This happens in active or passive
  64. * mode, and the ports will remain open even after ending the FTP session. FTP
  65. * "send" operations seem to close ports immediately. This behavior may cause
  66. * problems on some systems when downloading large sets of files.
  67. *
  68. * @since Ant 1.3
  69. */
  70. public class FTP
  71. extends Task {
  72. protected static final int SEND_FILES = 0;
  73. protected static final int GET_FILES = 1;
  74. protected static final int DEL_FILES = 2;
  75. protected static final int LIST_FILES = 3;
  76. protected static final int MK_DIR = 4;
  77. protected static final int CHMOD = 5;
  78. protected static final int RM_DIR = 6;
  79. /** return code of ftp - not implemented in commons-net version 1.0 */
  80. private static final int CODE_521 = 521;
  81. /** Default port for FTP */
  82. public static final int DEFAULT_FTP_PORT = 21;
  83. private String remotedir;
  84. private String server;
  85. private String userid;
  86. private String password;
  87. private File listing;
  88. private boolean binary = true;
  89. private boolean passive = false;
  90. private boolean verbose = false;
  91. private boolean newerOnly = false;
  92. private long timeDiffMillis = 0;
  93. private boolean timeDiffAuto = false;
  94. private int action = SEND_FILES;
  95. private Vector filesets = new Vector();
  96. private Vector dirCache = new Vector();
  97. private int transferred = 0;
  98. private String remoteFileSep = "/";
  99. private int port = DEFAULT_FTP_PORT;
  100. private boolean skipFailedTransfers = false;
  101. private int skipped = 0;
  102. private boolean ignoreNoncriticalErrors = false;
  103. private boolean preserveLastModified = false;
  104. private String chmod = null;
  105. private String umask = null;
  106. private FileUtils fileUtils = FileUtils.newFileUtils();
  107. protected static final String[] ACTION_STRS = {
  108. "sending",
  109. "getting",
  110. "deleting",
  111. "listing",
  112. "making directory",
  113. "chmod",
  114. "removing"
  115. };
  116. protected static final String[] COMPLETED_ACTION_STRS = {
  117. "sent",
  118. "retrieved",
  119. "deleted",
  120. "listed",
  121. "created directory",
  122. "mode changed",
  123. "removed"
  124. };
  125. protected static final String[] ACTION_TARGET_STRS = {
  126. "files",
  127. "files",
  128. "files",
  129. "files",
  130. "directory",
  131. "files",
  132. "directories"
  133. };
  134. /**
  135. * internal class allowing to read the contents of a remote file system
  136. * using the FTP protocol
  137. * used in particular for ftp get operations
  138. * differences with DirectoryScanner
  139. * "" (the root of the fileset) is never included in the included directories
  140. * followSymlinks defaults to false
  141. */
  142. protected class FTPDirectoryScanner extends DirectoryScanner {
  143. protected FTPClient ftp = null;
  144. private String rootPath = null;
  145. /**
  146. * since ant 1.6
  147. * this flag should be set to true on UNIX and can save scanning time
  148. */
  149. private boolean remoteSystemCaseSensitive = false;
  150. private boolean remoteSensitivityChecked = false;
  151. /**
  152. * constructor
  153. * @param ftp ftpclient object
  154. */
  155. public FTPDirectoryScanner(FTPClient ftp) {
  156. super();
  157. this.ftp = ftp;
  158. this.setFollowSymlinks(false);
  159. }
  160. /**
  161. * scans the remote directory,
  162. * storing internally the included files, directories, ...
  163. */
  164. public void scan() {
  165. if (includes == null) {
  166. // No includes supplied, so set it to 'matches all'
  167. includes = new String[1];
  168. includes[0] = "**";
  169. }
  170. if (excludes == null) {
  171. excludes = new String[0];
  172. }
  173. filesIncluded = new Vector();
  174. filesNotIncluded = new Vector();
  175. filesExcluded = new Vector();
  176. dirsIncluded = new Vector();
  177. dirsNotIncluded = new Vector();
  178. dirsExcluded = new Vector();
  179. try {
  180. String cwd = ftp.printWorkingDirectory();
  181. // always start from the current ftp working dir
  182. checkIncludePatterns();
  183. clearCaches();
  184. ftp.changeWorkingDirectory(cwd);
  185. } catch (IOException e) {
  186. throw new BuildException("Unable to scan FTP server: ", e);
  187. }
  188. }
  189. /**
  190. * this routine is actually checking all the include patterns in
  191. * order to avoid scanning everything under base dir
  192. * @since ant1.6
  193. */
  194. private void checkIncludePatterns() {
  195. Hashtable newroots = new Hashtable();
  196. // put in the newroots vector the include patterns without
  197. // wildcard tokens
  198. for (int icounter = 0; icounter < includes.length; icounter++) {
  199. String newpattern =
  200. SelectorUtils.rtrimWildcardTokens(includes[icounter]);
  201. newroots.put(newpattern, includes[icounter]);
  202. }
  203. if (remotedir == null) {
  204. try {
  205. remotedir = ftp.printWorkingDirectory();
  206. } catch (IOException e) {
  207. throw new BuildException("could not read current ftp directory",
  208. getLocation());
  209. }
  210. }
  211. AntFTPFile baseFTPFile = new AntFTPRootFile(ftp, remotedir);
  212. rootPath = baseFTPFile.getAbsolutePath();
  213. // construct it
  214. if (newroots.containsKey("")) {
  215. // we are going to scan everything anyway
  216. scandir(rootPath, "", true);
  217. } else {
  218. // only scan directories that can include matched files or
  219. // directories
  220. Enumeration enum2 = newroots.keys();
  221. while (enum2.hasMoreElements()) {
  222. String currentelement = (String) enum2.nextElement();
  223. String originalpattern = (String) newroots.get(currentelement);
  224. AntFTPFile myfile = new AntFTPFile(baseFTPFile, currentelement);
  225. boolean isOK = true;
  226. boolean traversesSymlinks = false;
  227. String path = null;
  228. if (myfile.exists()) {
  229. if (remoteSensitivityChecked
  230. && remoteSystemCaseSensitive && isFollowSymlinks()) {
  231. // cool case,
  232. //we do not need to scan all the subdirs in the relative path
  233. path = myfile.getFastRelativePath();
  234. } else {
  235. // may be on a case insensitive file system. We want
  236. // the results to show what's really on the disk, so
  237. // we need to double check.
  238. try {
  239. path = myfile.getRelativePath();
  240. traversesSymlinks = myfile.isTraverseSymlinks();
  241. } catch (IOException be) {
  242. throw new BuildException(be, getLocation());
  243. } catch (BuildException be) {
  244. isOK = false;
  245. }
  246. }
  247. } else {
  248. isOK = false;
  249. }
  250. if (isOK) {
  251. currentelement = path.replace(remoteFileSep.charAt(0), File.separatorChar);
  252. if (!isFollowSymlinks()
  253. && traversesSymlinks) {
  254. continue;
  255. }
  256. if (myfile.isDirectory()) {
  257. if (isIncluded(currentelement)
  258. && currentelement.length() > 0) {
  259. accountForIncludedDir(currentelement, myfile, true);
  260. } else {
  261. if (currentelement.length() > 0) {
  262. if (currentelement.charAt(currentelement
  263. .length() - 1)
  264. != File.separatorChar) {
  265. currentelement =
  266. currentelement + File.separatorChar;
  267. }
  268. }
  269. scandir(myfile.getAbsolutePath(), currentelement, true);
  270. }
  271. } else {
  272. if (isCaseSensitive
  273. && originalpattern.equals(currentelement)) {
  274. accountForIncludedFile(currentelement);
  275. } else if (!isCaseSensitive
  276. && originalpattern
  277. .equalsIgnoreCase(currentelement)) {
  278. accountForIncludedFile(currentelement);
  279. }
  280. }
  281. }
  282. }
  283. }
  284. }
  285. /**
  286. * scans a particular directory
  287. * @param dir directory to scan
  288. * @param vpath relative path to the base directory of the remote fileset
  289. * always ended with a File.separator
  290. * @param fast seems to be always true in practice
  291. */
  292. protected void scandir(String dir, String vpath, boolean fast) {
  293. // avoid double scanning of directories, can only happen in fast mode
  294. if (fast && hasBeenScanned(vpath)) {
  295. return;
  296. }
  297. try {
  298. if (!ftp.changeWorkingDirectory(dir)) {
  299. return;
  300. }
  301. String completePath = null;
  302. if (!vpath.equals("")) {
  303. completePath = rootPath + remoteFileSep
  304. + vpath.replace(File.separatorChar, remoteFileSep.charAt(0));
  305. } else {
  306. completePath = rootPath;
  307. }
  308. FTPFile[] newfiles = listFiles(completePath, false);
  309. if (newfiles == null) {
  310. ftp.changeToParentDirectory();
  311. return;
  312. }
  313. for (int i = 0; i < newfiles.length; i++) {
  314. FTPFile file = newfiles[i];
  315. if (!file.getName().equals(".")
  316. && !file.getName().equals("..")) {
  317. if (isFunctioningAsDirectory(ftp, dir, file)) {
  318. String name = vpath + file.getName();
  319. boolean slowScanAllowed = true;
  320. if (!isFollowSymlinks() && file.isSymbolicLink()) {
  321. dirsExcluded.addElement(name);
  322. slowScanAllowed = false;
  323. } else if (isIncluded(name)) {
  324. accountForIncludedDir(name,
  325. new AntFTPFile(ftp, file, completePath) , fast);
  326. } else {
  327. dirsNotIncluded.addElement(name);
  328. if (fast && couldHoldIncluded(name)) {
  329. scandir(file.getName(),
  330. name + File.separator, fast);
  331. }
  332. }
  333. if (!fast && slowScanAllowed) {
  334. scandir(file.getName(),
  335. name + File.separator, fast);
  336. }
  337. } else {
  338. String name = vpath + file.getName();
  339. if (!isFollowSymlinks() && file.isSymbolicLink()) {
  340. filesExcluded.addElement(name);
  341. } else if (isFunctioningAsFile(ftp, dir, file)) {
  342. accountForIncludedFile(name);
  343. }
  344. }
  345. }
  346. }
  347. ftp.changeToParentDirectory();
  348. } catch (IOException e) {
  349. throw new BuildException("Error while communicating with FTP "
  350. + "server: ", e);
  351. }
  352. }
  353. /**
  354. * process included file
  355. * @param name path of the file relative to the directory of the fileset
  356. */
  357. private void accountForIncludedFile(String name) {
  358. if (!filesIncluded.contains(name)
  359. && !filesExcluded.contains(name)) {
  360. if (isIncluded(name)) {
  361. if (!isExcluded(name)) {
  362. filesIncluded.addElement(name);
  363. } else {
  364. filesExcluded.addElement(name);
  365. }
  366. } else {
  367. filesNotIncluded.addElement(name);
  368. }
  369. }
  370. }
  371. /**
  372. *
  373. * @param name path of the directory relative to the directory of
  374. * the fileset
  375. * @param file directory as file
  376. * @param fast
  377. */
  378. private void accountForIncludedDir(String name, AntFTPFile file, boolean fast) {
  379. if (!dirsIncluded.contains(name)
  380. && !dirsExcluded.contains(name)) {
  381. if (!isExcluded(name)) {
  382. if (fast) {
  383. if (file.isSymbolicLink()) {
  384. try {
  385. file.getClient().changeWorkingDirectory(file.curpwd);
  386. } catch (IOException ioe) {
  387. throw new BuildException("could not change directory to curpwd");
  388. }
  389. scandir(file.getLink(),
  390. name + File.separator, fast);
  391. } else {
  392. try {
  393. file.getClient().changeWorkingDirectory(file.curpwd);
  394. } catch (IOException ioe) {
  395. throw new BuildException("could not change directory to curpwd");
  396. }
  397. scandir(file.getName(),
  398. name + File.separator, fast);
  399. }
  400. }
  401. dirsIncluded.addElement(name);
  402. } else {
  403. dirsExcluded.addElement(name);
  404. if (fast && couldHoldIncluded(name)) {
  405. try {
  406. file.getClient().changeWorkingDirectory(file.curpwd);
  407. } catch (IOException ioe) {
  408. throw new BuildException("could not change directory to curpwd");
  409. }
  410. scandir(file.getName(),
  411. name + File.separator, fast);
  412. }
  413. }
  414. }
  415. }
  416. /**
  417. * temporary table to speed up the various scanning methods below
  418. *
  419. * @since Ant 1.6
  420. */
  421. private Map fileListMap = new HashMap();
  422. /**
  423. * List of all scanned directories.
  424. *
  425. * @since Ant 1.6
  426. */
  427. private Set scannedDirs = new HashSet();
  428. /**
  429. * Has the directory with the given path relative to the base
  430. * directory already been scanned?
  431. *
  432. * <p>Registers the given directory as scanned as a side effect.</p>
  433. *
  434. * @since Ant 1.6
  435. */
  436. private boolean hasBeenScanned(String vpath) {
  437. return !scannedDirs.add(vpath);
  438. }
  439. /**
  440. * Clear internal caches.
  441. *
  442. * @since Ant 1.6
  443. */
  444. private void clearCaches() {
  445. fileListMap.clear();
  446. scannedDirs.clear();
  447. }
  448. /**
  449. * list the files present in one directory.
  450. * @param directory full path on the remote side
  451. * @param changedir if true change to directory directory before listing
  452. * @return array of FTPFile
  453. */
  454. public FTPFile[] listFiles(String directory, boolean changedir) {
  455. //getProject().log("listing files in directory " + directory, Project.MSG_DEBUG);
  456. String currentPath = directory;
  457. if (changedir) {
  458. try {
  459. boolean result = ftp.changeWorkingDirectory(directory);
  460. if (!result) {
  461. return null;
  462. }
  463. currentPath = ftp.printWorkingDirectory();
  464. } catch (IOException ioe) {
  465. throw new BuildException(ioe, getLocation());
  466. }
  467. }
  468. if (fileListMap.containsKey(currentPath)) {
  469. getProject().log("filelist map used in listing files", Project.MSG_DEBUG);
  470. return ((FTPFile[]) fileListMap.get(currentPath));
  471. }
  472. FTPFile[] result = null;
  473. try {
  474. result = ftp.listFiles();
  475. } catch (IOException ioe) {
  476. throw new BuildException(ioe, getLocation());
  477. }
  478. fileListMap.put(currentPath, result);
  479. if (!remoteSensitivityChecked) {
  480. checkRemoteSensitivity(result, directory);
  481. }
  482. return result;
  483. }
  484. /**
  485. * cd into one directory and
  486. * list the files present in one directory.
  487. * @param directory full path on the remote side
  488. * @return array of FTPFile
  489. */
  490. public FTPFile[] listFiles(String directory) {
  491. return listFiles(directory, true);
  492. }
  493. private void checkRemoteSensitivity(FTPFile[] array, String directory) {
  494. if (array == null) {
  495. return;
  496. }
  497. boolean candidateFound = false;
  498. String target = null;
  499. for (int icounter = 0; icounter < array.length; icounter++) {
  500. if (array[icounter].isDirectory()) {
  501. if (!array[icounter].getName().equals(".")
  502. && !array[icounter].getName().equals("..")) {
  503. candidateFound = true;
  504. target = fiddleName(array[icounter].getName());
  505. getProject().log("will try to cd to "
  506. + target + " where a directory called " + array[icounter].getName()
  507. + " exists", Project.MSG_DEBUG);
  508. for (int pcounter = 0; pcounter < array.length; pcounter++) {
  509. if (array[pcounter].getName().equals(target) && pcounter != icounter) {
  510. candidateFound = false;
  511. }
  512. }
  513. if (candidateFound) {
  514. break;
  515. }
  516. }
  517. }
  518. }
  519. if (candidateFound) {
  520. try {
  521. getProject().log("testing case sensitivity, attempting to cd to "
  522. + target, Project.MSG_DEBUG);
  523. remoteSystemCaseSensitive = !ftp.changeWorkingDirectory(target);
  524. } catch (IOException ioe) {
  525. remoteSystemCaseSensitive = true;
  526. } finally {
  527. try {
  528. ftp.changeWorkingDirectory(directory);
  529. } catch (IOException ioe) {
  530. throw new BuildException(ioe, getLocation());
  531. }
  532. }
  533. getProject().log("remote system is case sensitive : " + remoteSystemCaseSensitive,
  534. Project.MSG_VERBOSE);
  535. remoteSensitivityChecked = true;
  536. }
  537. }
  538. private String fiddleName(String origin) {
  539. StringBuffer result = new StringBuffer();
  540. for (int icounter = 0; icounter < origin.length(); icounter++) {
  541. if (Character.isLowerCase(origin.charAt(icounter))) {
  542. result.append(Character.toUpperCase(origin.charAt(icounter)));
  543. } else if (Character.isUpperCase(origin.charAt(icounter))) {
  544. result.append(Character.toLowerCase(origin.charAt(icounter)));
  545. } else {
  546. result.append(origin.charAt(icounter));
  547. }
  548. }
  549. return result.toString();
  550. }
  551. /**
  552. * an AntFTPFile is a representation of a remote file
  553. * @since Ant 1.6
  554. */
  555. protected class AntFTPFile {
  556. /**
  557. * ftp client
  558. */
  559. private FTPClient client;
  560. /**
  561. * parent directory of the file
  562. */
  563. private String curpwd;
  564. /**
  565. * the file itself
  566. */
  567. private FTPFile ftpFile;
  568. /**
  569. *
  570. */
  571. private AntFTPFile parent = null;
  572. private boolean relativePathCalculated = false;
  573. private boolean traversesSymlinks = false;
  574. private String relativePath = "";
  575. /**
  576. * constructor
  577. * @param client ftp client variable
  578. * @param ftpFile the file
  579. * @param curpwd absolute remote path where the file is found
  580. */
  581. public AntFTPFile(FTPClient client, FTPFile ftpFile, String curpwd) {
  582. this.client = client;
  583. this.ftpFile = ftpFile;
  584. this.curpwd = curpwd;
  585. }
  586. /**
  587. * other constructor
  588. * @param parent the parent file
  589. * @param path a relative path to the parent file
  590. */
  591. public AntFTPFile(AntFTPFile parent, String path) {
  592. this.parent = parent;
  593. this.client = parent.client;
  594. Vector pathElements = SelectorUtils.tokenizePath(path);
  595. try {
  596. boolean result = this.client.changeWorkingDirectory(parent.getAbsolutePath());
  597. //this should not happen, except if parent has been deleted by another process
  598. if (!result) {
  599. return;
  600. }
  601. this.curpwd = parent.getAbsolutePath();
  602. } catch (IOException ioe) {
  603. throw new BuildException("could not change working dir to "
  604. + parent.curpwd);
  605. }
  606. for (int fcount = 0; fcount < pathElements.size() - 1; fcount++) {
  607. String currentPathElement = (String) pathElements.elementAt(fcount);
  608. try {
  609. boolean result = this.client.changeWorkingDirectory(currentPathElement);
  610. if (!result && !isCaseSensitive()
  611. && (remoteSystemCaseSensitive || !remoteSensitivityChecked)) {
  612. currentPathElement = findPathElementCaseUnsensitive(this.curpwd,
  613. currentPathElement);
  614. if (currentPathElement == null) {
  615. return;
  616. }
  617. } else if (!result) {
  618. return;
  619. }
  620. this.curpwd = this.curpwd + remoteFileSep
  621. + currentPathElement;
  622. } catch (IOException ioe) {
  623. throw new BuildException("could not change working dir to "
  624. + (String) pathElements.elementAt(fcount)
  625. + " from " + this.curpwd);
  626. }
  627. }
  628. String lastpathelement = (String) pathElements.elementAt(pathElements.size() - 1);
  629. FTPFile [] theFiles = listFiles(this.curpwd);
  630. this.ftpFile = getFile(theFiles, lastpathelement);
  631. }
  632. /**
  633. * find a file in a directory in case unsensitive way
  634. * @param parentPath where we are
  635. * @param soughtPathElement what is being sought
  636. * @return the first file found or null if not found
  637. */
  638. private String findPathElementCaseUnsensitive(String parentPath,
  639. String soughtPathElement) {
  640. // we are already in the right path, so the second parameter
  641. // is false
  642. FTPFile[] theFiles = listFiles(parentPath, false);
  643. if (theFiles == null) {
  644. return null;
  645. }
  646. for (int icounter = 0; icounter < theFiles.length; icounter++) {
  647. if (theFiles[icounter].getName().equalsIgnoreCase(soughtPathElement)) {
  648. return theFiles[icounter].getName();
  649. }
  650. }
  651. return null;
  652. }
  653. /**
  654. * find out if the file exists
  655. * @return true if the file exists
  656. */
  657. public boolean exists() {
  658. return (ftpFile != null);
  659. }
  660. /**
  661. * if the file is a symbolic link, find out to what it is pointing
  662. * @return the target of the symbolic link
  663. */
  664. public String getLink() {
  665. return ftpFile.getLink();
  666. }
  667. /**
  668. * get the name of the file
  669. * @return the name of the file
  670. */
  671. public String getName() {
  672. return ftpFile.getName();
  673. }
  674. /**
  675. * find out the absolute path of the file
  676. * @return absolute path as string
  677. */
  678. public String getAbsolutePath() {
  679. return curpwd + remoteFileSep + ftpFile.getName();
  680. }
  681. /**
  682. * find out the relative path assuming that the path used to construct
  683. * this AntFTPFile was spelled properly with regards to case.
  684. * This is OK on a case sensitive system such as UNIX
  685. * @return relative path
  686. */
  687. public String getFastRelativePath() {
  688. String absPath = getAbsolutePath();
  689. if (absPath.indexOf(rootPath + remoteFileSep) == 0) {
  690. return absPath.substring(rootPath.length() + remoteFileSep.length());
  691. }
  692. return null;
  693. }
  694. /**
  695. * find out the relative path to the rootPath of the enclosing scanner.
  696. * this relative path is spelled exactly like on disk,
  697. * for instance if the AntFTPFile has been instantiated as ALPHA,
  698. * but the file is really called alpha, this method will return alpha.
  699. * If a symbolic link is encountered, it is followed, but the name of the link
  700. * rather than the name of the target is returned.
  701. * (ie does not behave like File.getCanonicalPath())
  702. * @return relative path, separated by remoteFileSep
  703. * @throws IOException if a change directory fails, ...
  704. * @throws BuildException if one of the components of the relative path cannot
  705. * be found.
  706. */
  707. public String getRelativePath() throws IOException, BuildException {
  708. if (!relativePathCalculated) {
  709. if (parent != null) {
  710. traversesSymlinks = parent.isTraverseSymlinks();
  711. relativePath = getRelativePath(parent.getAbsolutePath(),
  712. parent.getRelativePath());
  713. } else {
  714. relativePath = getRelativePath(rootPath, "");
  715. relativePathCalculated = true;
  716. }
  717. }
  718. return relativePath;
  719. }
  720. /**
  721. * get thge relative path of this file
  722. * @param currentPath base path
  723. * @param currentRelativePath relative path of the base path with regards to remote dir
  724. * @return relative path
  725. */
  726. private String getRelativePath(String currentPath, String currentRelativePath) {
  727. Vector pathElements = SelectorUtils.tokenizePath(getAbsolutePath(), remoteFileSep);
  728. Vector pathElements2 = SelectorUtils.tokenizePath(currentPath, remoteFileSep);
  729. String relPath = currentRelativePath;
  730. for (int pcount = pathElements2.size(); pcount < pathElements.size(); pcount++) {
  731. String currentElement = (String) pathElements.elementAt(pcount);
  732. FTPFile[] theFiles = listFiles(currentPath);
  733. FTPFile theFile = null;
  734. if (theFiles != null) {
  735. theFile = getFile(theFiles, currentElement);
  736. }
  737. if (theFile == null) {
  738. throw new BuildException("could not find " + currentElement
  739. + " from " + currentPath);
  740. } else {
  741. traversesSymlinks = traversesSymlinks || theFile.isSymbolicLink();
  742. if (!relPath.equals("")) {
  743. relPath = relPath + remoteFileSep;
  744. }
  745. relPath = relPath + theFile.getName();
  746. currentPath = currentPath + remoteFileSep + theFile.getName();
  747. }
  748. }
  749. return relPath;
  750. }
  751. /**
  752. * find a file matching a string in an array of FTPFile.
  753. * This method will find "alpha" when requested for "ALPHA"
  754. * if and only if the caseSensitive attribute is set to false.
  755. * When caseSensitive is set to true, only the exact match is returned.
  756. * @param theFiles array of files
  757. * @param lastpathelement the file name being sought
  758. * @return null if the file cannot be found, otherwise return the matching file.
  759. */
  760. public FTPFile getFile(FTPFile[] theFiles, String lastpathelement) {
  761. if (theFiles == null) {
  762. return null;
  763. }
  764. for (int fcount = 0; fcount < theFiles.length; fcount++) {
  765. if (theFiles[fcount].getName().equals(lastpathelement)) {
  766. return theFiles[fcount];
  767. } else if (!isCaseSensitive()
  768. && theFiles[fcount].getName().equalsIgnoreCase(lastpathelement)) {
  769. return theFiles[fcount];
  770. }
  771. }
  772. return null;
  773. }
  774. /**
  775. * tell if a file is a directory.
  776. * note that it will return false for symbolic links pointing to directories.
  777. * @return <code>true</code> for directories
  778. */
  779. public boolean isDirectory() {
  780. return ftpFile.isDirectory();
  781. }
  782. /**
  783. * tell if a file is a symbolic link
  784. * @return <code>true</code> for symbolic links
  785. */
  786. public boolean isSymbolicLink() {
  787. return ftpFile.isSymbolicLink();
  788. }
  789. /**
  790. * return the attached FTP client object.
  791. * Warning : this instance is really shared with the enclosing class.
  792. * @return FTP client
  793. */
  794. protected FTPClient getClient() {
  795. return client;
  796. }
  797. /**
  798. * sets the current path of an AntFTPFile
  799. * @param curpwd the current path one wants to set
  800. */
  801. protected void setCurpwd(String curpwd) {
  802. this.curpwd = curpwd;
  803. }
  804. /**
  805. * returns the path of the directory containing the AntFTPFile.
  806. * of the full path of the file itself in case of AntFTPRootFile
  807. * @return parent directory of the AntFTPFile
  808. */
  809. public String getCurpwd() {
  810. return curpwd;
  811. }
  812. /**
  813. * find out if a symbolic link is encountered in the relative path of this file
  814. * from rootPath.
  815. * @return <code>true</code> if a symbolic link is encountered in the relative path.
  816. * @throws IOException if one of the change directory or directory listing operations
  817. * fails
  818. * @throws BuildException if a path component in the relative path cannot be found.
  819. */
  820. public boolean isTraverseSymlinks() throws IOException, BuildException {
  821. if (!relativePathCalculated) {
  822. // getRelativePath also finds about symlinks
  823. String relpath = getRelativePath();
  824. }
  825. return traversesSymlinks;
  826. }
  827. }
  828. /**
  829. * special class to represent the remote directory itself
  830. * @since Ant 1.6
  831. */
  832. protected class AntFTPRootFile extends AntFTPFile {
  833. private String remotedir;
  834. /**
  835. * constructor
  836. * @param aclient FTP client
  837. * @param remotedir remote directory
  838. */
  839. public AntFTPRootFile(FTPClient aclient, String remotedir) {
  840. super(aclient, null, remotedir);
  841. this.remotedir = remotedir;
  842. try {
  843. this.getClient().changeWorkingDirectory(this.remotedir);
  844. this.setCurpwd(this.getClient().printWorkingDirectory());
  845. } catch (IOException ioe) {
  846. throw new BuildException(ioe, getLocation());
  847. }
  848. }
  849. /**
  850. * find the absolute path
  851. * @return absolute path
  852. */
  853. public String getAbsolutePath() {
  854. return this.getCurpwd();
  855. }
  856. /**
  857. * find out the relative path to root
  858. * @return empty string
  859. * @throws BuildException actually never
  860. * @throws IOException actually never
  861. */
  862. public String getRelativePath() throws BuildException, IOException {
  863. return "";
  864. }
  865. }
  866. }
  867. /**
  868. * check FTPFiles to check whether they function as directories too
  869. * the FTPFile API seem to make directory and symbolic links incompatible
  870. * we want to find out if we can cd to a symbolic link
  871. * @param dir the parent directory of the file to test
  872. * @param file the file to test
  873. * @return true if it is possible to cd to this directory
  874. * @since ant 1.6
  875. */
  876. private boolean isFunctioningAsDirectory(FTPClient ftp, String dir, FTPFile file) {
  877. boolean result = false;
  878. String currentWorkingDir = null;
  879. if (file.isDirectory()) {
  880. return true;
  881. } else if (file.isFile()) {
  882. return false;
  883. }
  884. try {
  885. currentWorkingDir = ftp.printWorkingDirectory();
  886. } catch (IOException ioe) {
  887. getProject().log("could not find current working directory " + dir
  888. + " while checking a symlink",
  889. Project.MSG_DEBUG);
  890. }
  891. if (currentWorkingDir != null) {
  892. try {
  893. result = ftp.changeWorkingDirectory(file.getLink());
  894. } catch (IOException ioe) {
  895. getProject().log("could not cd to " + file.getLink() + " while checking a symlink",
  896. Project.MSG_DEBUG);
  897. }
  898. if (result) {
  899. boolean comeback = false;
  900. try {
  901. comeback = ftp.changeWorkingDirectory(currentWorkingDir);
  902. } catch (IOException ioe) {
  903. getProject().log("could not cd back to " + dir + " while checking a symlink",
  904. Project.MSG_ERR);
  905. } finally {
  906. if (!comeback) {
  907. throw new BuildException("could not cd back to " + dir
  908. + " while checking a symlink");
  909. }
  910. }
  911. }
  912. }
  913. return result;
  914. }
  915. /**
  916. * check FTPFiles to check whether they function as directories too
  917. * the FTPFile API seem to make directory and symbolic links incompatible
  918. * we want to find out if we can cd to a symbolic link
  919. * @param dir the parent directory of the file to test
  920. * @param file the file to test
  921. * @return true if it is possible to cd to this directory
  922. * @since ant 1.6
  923. */
  924. private boolean isFunctioningAsFile(FTPClient ftp, String dir, FTPFile file) {
  925. if (file.isDirectory()) {
  926. return false;
  927. } else if (file.isFile()) {
  928. return true;
  929. }
  930. return !isFunctioningAsDirectory(ftp, dir, file);
  931. }
  932. /**
  933. * Sets the remote directory where files will be placed. This may be a
  934. * relative or absolute path, and must be in the path syntax expected by
  935. * the remote server. No correction of path syntax will be performed.
  936. *
  937. * @param dir the remote directory name.
  938. */
  939. public void setRemotedir(String dir) {
  940. this.remotedir = dir;
  941. }
  942. /**
  943. * Sets the FTP server to send files to.
  944. *
  945. * @param server the remote server name.
  946. */
  947. public void setServer(String server) {
  948. this.server = server;
  949. }
  950. /**
  951. * Sets the FTP port used by the remote server.
  952. *
  953. * @param port the port on which the remote server is listening.
  954. */
  955. public void setPort(int port) {
  956. this.port = port;
  957. }
  958. /**
  959. * Sets the login user id to use on the specified server.
  960. *
  961. * @param userid remote system userid.
  962. */
  963. public void setUserid(String userid) {
  964. this.userid = userid;
  965. }
  966. /**
  967. * Sets the login password for the given user id.
  968. *
  969. * @param password the password on the remote system.
  970. */
  971. public void setPassword(String password) {
  972. this.password = password;
  973. }
  974. /**
  975. * If true, uses binary mode, otherwise text mode (default is binary).
  976. *
  977. * @param binary if true use binary mode in transfers.
  978. */
  979. public void setBinary(boolean binary) {
  980. this.binary = binary;
  981. }
  982. /**
  983. * Specifies whether to use passive mode. Set to true if you are behind a
  984. * firewall and cannot connect without it. Passive mode is disabled by
  985. * default.
  986. *
  987. * @param passive true is passive mode should be used.
  988. */
  989. public void setPassive(boolean passive) {
  990. this.passive = passive;
  991. }
  992. /**
  993. * Set to true to receive notification about each file as it is
  994. * transferred.
  995. *
  996. * @param verbose true if verbose notifications are required.
  997. */
  998. public void setVerbose(boolean verbose) {
  999. this.verbose = verbose;
  1000. }
  1001. /**
  1002. * A synonym for <tt>depends</tt>. Set to true to transmit only new
  1003. * or changed files.
  1004. *
  1005. * See the related attributes timediffmillis and timediffauto.
  1006. *
  1007. * @param newer if true only transfer newer files.
  1008. */
  1009. public void setNewer(boolean newer) {
  1010. this.newerOnly = newer;
  1011. }
  1012. /**
  1013. * number of milliseconds to add to the time on the remote machine
  1014. * to get the time on the local machine.
  1015. *
  1016. * use in conjunction with <code>newer</code>
  1017. *
  1018. * @param timeDiffMillis number of milliseconds
  1019. *
  1020. * @since ant 1.6
  1021. */
  1022. public void setTimeDiffMillis(long timeDiffMillis) {
  1023. this.timeDiffMillis = timeDiffMillis;
  1024. }
  1025. /**
  1026. * "true" to find out automatically the time difference
  1027. * between local and remote machine.
  1028. *
  1029. * This requires right to create
  1030. * and delete a temporary file in the remote directory.
  1031. *
  1032. * @param timeDiffAuto true = find automatically the time diff
  1033. *
  1034. * @since ant 1.6
  1035. */
  1036. public void setTimeDiffAuto(boolean timeDiffAuto) {
  1037. this.timeDiffAuto = timeDiffAuto;
  1038. }
  1039. /**
  1040. * Set to true to preserve modification times for "gotten" files.
  1041. *
  1042. * @param preserveLastModified if true preserver modification times.
  1043. */
  1044. public void setPreserveLastModified(boolean preserveLastModified) {
  1045. this.preserveLastModified = preserveLastModified;
  1046. }
  1047. /**
  1048. * Set to true to transmit only files that are new or changed from their
  1049. * remote counterparts. The default is to transmit all files.
  1050. *
  1051. * @param depends if true only transfer newer files.
  1052. */
  1053. public void setDepends(boolean depends) {
  1054. this.newerOnly = depends;
  1055. }
  1056. /**
  1057. * Sets the remote file separator character. This normally defaults to the
  1058. * Unix standard forward slash, but can be manually overridden using this
  1059. * call if the remote server requires some other separator. Only the first
  1060. * character of the string is used.
  1061. *
  1062. * @param separator the file separator on the remote system.
  1063. */
  1064. public void setSeparator(String separator) {
  1065. remoteFileSep = separator;
  1066. }
  1067. /**
  1068. * Sets the file permission mode (Unix only) for files sent to the
  1069. * server.
  1070. *
  1071. * @param theMode unix style file mode for the files sent to the remote
  1072. * system.
  1073. */
  1074. public void setChmod(String theMode) {
  1075. this.chmod = theMode;
  1076. }
  1077. /**
  1078. * Sets the default mask for file creation on a unix server.
  1079. *
  1080. * @param theUmask unix style umask for files created on the remote server.
  1081. */
  1082. public void setUmask(String theUmask) {
  1083. this.umask = theUmask;
  1084. }
  1085. /**
  1086. * A set of files to upload or download
  1087. *
  1088. * @param set the set of files to be added to the list of files to be
  1089. * transferred.
  1090. */
  1091. public void addFileset(FileSet set) {
  1092. filesets.addElement(set);
  1093. }
  1094. /**
  1095. * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
  1096. * "mkdir" and "list".
  1097. *
  1098. * @deprecated setAction(String) is deprecated and is replaced with
  1099. * setAction(FTP.Action) to make Ant's Introspection mechanism do the
  1100. * work and also to encapsulate operations on the type in its own
  1101. * class.
  1102. * @ant.attribute ignore="true"
  1103. *
  1104. * @param action the FTP action to be performed.
  1105. *
  1106. * @throws BuildException if the action is not a valid action.
  1107. */
  1108. public void setAction(String action) throws BuildException {
  1109. log("DEPRECATED - The setAction(String) method has been deprecated."
  1110. + " Use setAction(FTP.Action) instead.");
  1111. Action a = new Action();
  1112. a.setValue(action);
  1113. this.action = a.getAction();
  1114. }
  1115. /**
  1116. * Sets the FTP action to be taken. Currently accepts "put", "get", "del",
  1117. * "mkdir", "chmod" and "list".
  1118. *
  1119. * @param action the FTP action to be performed.
  1120. *
  1121. * @throws BuildException if the action is not a valid action.
  1122. */
  1123. public void setAction(Action action) throws BuildException {
  1124. this.action = action.getAction();
  1125. }
  1126. /**
  1127. * The output file for the "list" action. This attribute is ignored for
  1128. * any other actions.
  1129. *
  1130. * @param listing file in which to store the listing.
  1131. */
  1132. public void setListing(File listing) {
  1133. this.listing = listing;
  1134. }
  1135. /**
  1136. * If true, enables unsuccessful file put, delete and get
  1137. * operations to be skipped with a warning and the remainder
  1138. * of the files still transferred.
  1139. *
  1140. * @param skipFailedTransfers true if failures in transfers are ignored.
  1141. */
  1142. public void setSkipFailedTransfers(boolean skipFailedTransfers) {
  1143. this.skipFailedTransfers = skipFailedTransfers;
  1144. }
  1145. /**
  1146. * set the flag to skip errors on directory creation.
  1147. * (and maybe later other server specific errors)
  1148. *
  1149. * @param ignoreNoncriticalErrors true if non-critical errors should not
  1150. * cause a failure.
  1151. */
  1152. public void setIgnoreNoncriticalErrors(boolean ignoreNoncriticalErrors) {
  1153. this.ignoreNoncriticalErrors = ignoreNoncriticalErrors;
  1154. }
  1155. /**
  1156. * Checks to see that all required parameters are set.
  1157. *
  1158. * @throws BuildException if the configuration is not valid.
  1159. */
  1160. protected void checkConfiguration() throws BuildException {
  1161. if (server == null) {
  1162. throw new BuildException("server attribute must be set!");
  1163. }
  1164. if (userid == null) {
  1165. throw new BuildException("userid attribute must be set!");
  1166. }
  1167. if (password == null) {
  1168. throw new BuildException("password attribute must be set!");
  1169. }
  1170. if ((action == LIST_FILES) && (listing == null)) {
  1171. throw new BuildException("listing attribute must be set for list "
  1172. + "action!");
  1173. }
  1174. if (action == MK_DIR && remotedir == null) {
  1175. throw new BuildException("remotedir attribute must be set for "
  1176. + "mkdir action!");
  1177. }
  1178. if (action == CHMOD && chmod == null) {
  1179. throw new BuildException("chmod attribute must be set for chmod "
  1180. + "action!");
  1181. }
  1182. }
  1183. /**
  1184. * For each file in the fileset, do the appropriate action: send, get,
  1185. * delete, or list.
  1186. *
  1187. * @param ftp the FTPClient instance used to perform FTP actions
  1188. * @param fs the fileset on which the actions are performed.
  1189. *
  1190. * @return the number of files to be transferred.
  1191. *
  1192. * @throws IOException if there is a problem reading a file
  1193. * @throws BuildException if there is a problem in the configuration.
  1194. */
  1195. protected int transferFiles(FTPClient ftp, FileSet fs)
  1196. throws IOException, BuildException {
  1197. DirectoryScanner ds;
  1198. if (action == SEND_FILES) {
  1199. ds = fs.getDirectoryScanner(getProject());
  1200. } else {
  1201. // warn that selectors are not supported
  1202. if (fs.getSelectors(getProject()).length != 0) {
  1203. getProject().log("selectors are not supported in remote filesets",
  1204. Project.MSG_WARN);
  1205. }
  1206. ds = new FTPDirectoryScanner(ftp);
  1207. fs.setupDirectoryScanner(ds, getProject());
  1208. ds.setFollowSymlinks(fs.isFollowSymlinks());
  1209. ds.scan();
  1210. }
  1211. String[] dsfiles = null;
  1212. if (action == RM_DIR) {
  1213. dsfiles = ds.getIncludedDirectories();
  1214. } else {
  1215. dsfiles = ds.getIncludedFiles();
  1216. }
  1217. String dir = null;
  1218. if ((ds.getBasedir() == null)
  1219. && ((action == SEND_FILES) || (action == GET_FILES))) {
  1220. throw new BuildException("the dir attribute must be set for send "
  1221. + "and get actions");
  1222. } else {
  1223. if ((action == SEND_FILES) || (action == GET_FILES)) {
  1224. dir = ds.getBasedir().getAbsolutePath();
  1225. }
  1226. }
  1227. // If we are doing a listing, we need the output stream created now.
  1228. BufferedWriter bw = null;
  1229. try {
  1230. if (action == LIST_FILES) {
  1231. File pd = fileUtils.getParentFile(listing);
  1232. if (!pd.exists()) {
  1233. pd.mkdirs();
  1234. }
  1235. bw = new BufferedWriter(new FileWriter(listing));
  1236. }
  1237. if (action == RM_DIR) {
  1238. // to remove directories, start by the end of the list
  1239. // the trunk does not let itself be removed before the leaves
  1240. for (int i = dsfiles.length - 1; i >= 0; i--) {
  1241. rmDir(ftp, dsfiles[i]);
  1242. }
  1243. } else {
  1244. for (int i = 0; i < dsfiles.length; i++) {
  1245. switch (action) {
  1246. case SEND_FILES:
  1247. sendFile(ftp, dir, dsfiles[i]);
  1248. break;
  1249. case GET_FILES:
  1250. getFile(ftp, dir, dsfiles[i]);
  1251. break;
  1252. case DEL_FILES:
  1253. delFile(ftp, dsfiles[i]);
  1254. break;
  1255. case LIST_FILES:
  1256. listFile(ftp, bw, dsfiles[i]);
  1257. break;
  1258. case CHMOD:
  1259. doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(dsfiles[i]));
  1260. transferred++;
  1261. break;
  1262. default:
  1263. throw new BuildException("unknown ftp action " + action);
  1264. }
  1265. }
  1266. }
  1267. } finally {
  1268. if (bw != null) {
  1269. bw.close();
  1270. }
  1271. }
  1272. return dsfiles.length;
  1273. }
  1274. /**
  1275. * Sends all files specified by the configured filesets to the remote
  1276. * server.
  1277. *
  1278. * @param ftp the FTPClient instance used to perform FTP actions
  1279. *
  1280. * @throws IOException if there is a problem reading a file
  1281. * @throws BuildException if there is a problem in the configuration.
  1282. */
  1283. protected void transferFiles(FTPClient ftp)
  1284. throws IOException, BuildException {
  1285. transferred = 0;
  1286. skipped = 0;
  1287. if (filesets.size() == 0) {
  1288. throw new BuildException("at least one fileset must be specified.");
  1289. } else {
  1290. // get files from filesets
  1291. for (int i = 0; i < filesets.size(); i++) {
  1292. FileSet fs = (FileSet) filesets.elementAt(i);
  1293. if (fs != null) {
  1294. transferFiles(ftp, fs);
  1295. }
  1296. }
  1297. }
  1298. log(transferred + " " + ACTION_TARGET_STRS[action] + " "
  1299. + COMPLETED_ACTION_STRS[action]);
  1300. if (skipped != 0) {
  1301. log(skipped + " " + ACTION_TARGET_STRS[action]
  1302. + " were not successfully " + COMPLETED_ACTION_STRS[action]);
  1303. }
  1304. }
  1305. /**
  1306. * Correct a file path to correspond to the remote host requirements. This
  1307. * implementation currently assumes that the remote end can handle
  1308. * Unix-style paths with forward-slash separators. This can be overridden
  1309. * with the <code>separator</code> task parameter. No attempt is made to
  1310. * determine what syntax is appropriate for the remote host.
  1311. *
  1312. * @param file the remote file name to be resolved
  1313. *
  1314. * @return the filename as it will appear on the server.
  1315. */
  1316. protected String resolveFile(String file) {
  1317. return file.replace(System.getProperty("file.separator").charAt(0),
  1318. remoteFileSep.charAt(0));
  1319. }
  1320. /**
  1321. * Creates all parent directories specified in a complete relative
  1322. * pathname. Attempts to create existing directories will not cause
  1323. * errors.
  1324. *
  1325. * @param ftp the FTP client instance to use to execute FTP actions on
  1326. * the remote server.
  1327. * @param filename the name of the file whose parents should be created.
  1328. * @throws IOException under non documented circumstances
  1329. * @throws BuildException if it is impossible to cd to a remote directory
  1330. *
  1331. */
  1332. protected void createParents(FTPClient ftp, String filename)
  1333. throws IOException, BuildException {
  1334. File dir = new File(filename);
  1335. if (dirCache.contains(dir)) {
  1336. return;
  1337. }
  1338. Vector parents = new Vector();
  1339. String dirname;
  1340. while ((dirname = dir.getParent()) != null) {
  1341. File checkDir = new File(dirname);
  1342. if (dirCache.contains(checkDir)) {
  1343. break;
  1344. }
  1345. dir = checkDir;
  1346. parents.addElement(dir);
  1347. }
  1348. // find first non cached dir
  1349. int i = parents.size() - 1;
  1350. if (i >= 0) {
  1351. String cwd = ftp.printWorkingDirectory();
  1352. String parent = dir.getParent();
  1353. if (parent != null) {
  1354. if (!ftp.changeWorkingDirectory(resolveFile(parent))) {
  1355. throw new BuildException("could not change to "
  1356. + "directory: " + ftp.getReplyString());
  1357. }
  1358. }
  1359. while (i >= 0) {
  1360. dir = (File) parents.elementAt(i--);
  1361. // check if dir exists by trying to change into it.
  1362. if (!ftp.changeWorkingDirectory(dir.getName())) {
  1363. // could not change to it - try to create it
  1364. log("creating remote directory "
  1365. + resolveFile(dir.getPath()), Project.MSG_VERBOSE);
  1366. if (!ftp.makeDirectory(dir.getName())) {
  1367. handleMkDirFailure(ftp);
  1368. }
  1369. if (!ftp.changeWorkingDirectory(dir.getName())) {
  1370. throw new BuildException("could not change to "
  1371. + "directory: " + ftp.getReplyString());
  1372. }
  1373. }
  1374. dirCache.addElement(dir);
  1375. }
  1376. ftp.changeWorkingDirectory(cwd);
  1377. }
  1378. }
  1379. /**
  1380. * auto find the time difference between local and remote
  1381. * @param ftp handle to ftp client
  1382. * @return number of millis to add to remote time to make it comparable to local time
  1383. * @since ant 1.6
  1384. */
  1385. private long getTimeDiff(FTPClient ftp) {
  1386. long returnValue = 0;
  1387. File tempFile = findFileName(ftp);
  1388. try {
  1389. // create a local temporary file
  1390. fileUtils.createNewFile(tempFile);
  1391. long localTimeStamp = tempFile.lastModified();
  1392. BufferedInputStream instream = new BufferedInputStream(new FileInputStream(tempFile));
  1393. ftp.storeFile(tempFile.getName(), instream);
  1394. instream.close();
  1395. boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
  1396. if (success) {
  1397. FTPFile [] ftpFiles = ftp.listFiles(tempFile.getName());
  1398. if (ftpFiles.length == 1) {
  1399. long remoteTimeStamp = ftpFiles[0].getTimestamp().getTime().getTime();
  1400. returnValue = remoteTimeStamp - localTimeStamp;
  1401. }
  1402. ftp.deleteFile(ftpFiles[0].getName());
  1403. }
  1404. // delegate the deletion of the local temp file to the delete task
  1405. // because of race conditions occuring on Windows
  1406. Delete mydelete = (Delete) getProject().createTask("delete");
  1407. mydelete.setFile(tempFile.getCanonicalFile());
  1408. mydelete.execute();
  1409. } catch (Exception e) {
  1410. throw new BuildException(e, getLocation());
  1411. }
  1412. return returnValue;
  1413. }
  1414. /**
  1415. * find a suitable name for local and remote temporary file
  1416. */
  1417. private File findFileName(FTPClient ftp) {
  1418. FTPFile [] theFiles = null;
  1419. final int maxIterations = 1000;
  1420. for (int counter = 1; counter < maxIterations; counter++) {
  1421. File localFile = fileUtils.createTempFile("ant" + Integer.toString(counter), ".tmp",
  1422. null);
  1423. String fileName = localFile.getName();
  1424. boolean found = false;
  1425. try {
  1426. if (counter == 1) {
  1427. theFiles = ftp.listFiles();
  1428. }
  1429. for (int counter2 = 0; counter2 < theFiles.length; counter2++) {
  1430. if (theFiles[counter2].getName().equals(fileName)) {
  1431. found = true;
  1432. break;
  1433. }
  1434. }
  1435. } catch (IOException ioe) {
  1436. throw new BuildException(ioe, getLocation());
  1437. }
  1438. if (!found) {
  1439. localFile.deleteOnExit();
  1440. return localFile;
  1441. }
  1442. }
  1443. return null;
  1444. }
  1445. /**
  1446. * Checks to see if the remote file is current as compared with the local
  1447. * file. Returns true if the target file is up to date.
  1448. * @param ftp ftpclient
  1449. * @param localFile local file
  1450. * @param remoteFile remote file
  1451. * @return true if the target file is up to date
  1452. * @throws IOException in unknown circumstances
  1453. * @throws BuildException if the date of the remote files cannot be found and the action is
  1454. * GET_FILES
  1455. */
  1456. protected boolean isUpToDate(FTPClient ftp, File localFile,
  1457. String remoteFile)
  1458. throws IOException, BuildException {
  1459. log("checking date for " + remoteFile, Project.MSG_VERBOSE);
  1460. FTPFile[] files = ftp.listFiles(remoteFile);
  1461. // For Microsoft's Ftp-Service an Array with length 0 is
  1462. // returned if configured to return listings in "MS-DOS"-Format
  1463. if (files == null || files.length == 0) {
  1464. // If we are sending files, then assume out of date.
  1465. // If we are getting files, then throw an error
  1466. if (action == SEND_FILES) {
  1467. log("Could not date test remote file: " + remoteFile
  1468. + "assuming out of date.", Project.MSG_VERBOSE);
  1469. return false;
  1470. } else {
  1471. throw new BuildException("could not date test remote file: "
  1472. + ftp.getReplyString());
  1473. }
  1474. }
  1475. long remoteTimestamp = files[0].getTimestamp().getTime().getTime();
  1476. long localTimestamp = localFile.lastModified();
  1477. if (this.action == SEND_FILES) {
  1478. return remoteTimestamp + timeDiffMillis > localTimestamp;
  1479. } else {
  1480. return localTimestamp > remoteTimestamp + timeDiffMillis;
  1481. }
  1482. }
  1483. /**
  1484. * Sends a site command to the ftp server
  1485. * @param ftp ftp client
  1486. * @param theCMD command to execute
  1487. * @throws IOException in unknown circumstances
  1488. * @throws BuildException in unknown circumstances
  1489. */
  1490. protected void doSiteCommand(FTPClient ftp, String theCMD)
  1491. throws IOException, BuildException {
  1492. boolean rc;
  1493. String[] myReply = null;
  1494. log("Doing Site Command: " + theCMD, Project.MSG_VERBOSE);
  1495. rc = ftp.sendSiteCommand(theCMD);
  1496. if (!rc) {
  1497. log("Failed to issue Site Command: " + theCMD, Project.MSG_WARN);
  1498. } else {
  1499. myReply = ftp.getReplyStrings();
  1500. for (int x = 0; x < myReply.length; x++) {
  1501. if (myReply[x].indexOf("200") == -1) {
  1502. log(myReply[x], Project.MSG_WARN);
  1503. }
  1504. }
  1505. }
  1506. }
  1507. /**
  1508. * Sends a single file to the remote host. <code>filename</code> may
  1509. * contain a relative path specification. When this is the case, <code>sendFile</code>
  1510. * will attempt to create any necessary parent directories before sending
  1511. * the file. The file will then be sent using the entire relative path
  1512. * spec - no attempt is made to change directories. It is anticipated that
  1513. * this may eventually cause problems with some FTP servers, but it
  1514. * simplifies the coding.
  1515. * @param ftp ftp client
  1516. * @param dir base directory of the file to be sent (local)
  1517. * @param filename relative path of the file to be send
  1518. * locally relative to dir
  1519. * remotely relative to the remotedir attribute
  1520. * @throws IOException in unknown circumstances
  1521. * @throws BuildException in unknown circumstances
  1522. */
  1523. protected void sendFile(FTPClient ftp, String dir, String filename)
  1524. throws IOException, BuildException {
  1525. InputStream instream = null;
  1526. try {
  1527. // XXX - why not simply new File(dir, filename)?
  1528. File file = getProject().resolveFile(new File(dir, filename).getPath());
  1529. if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
  1530. return;
  1531. }
  1532. if (verbose) {
  1533. log("transferring " + file.getAbsolutePath());
  1534. }
  1535. instream = new BufferedInputStream(new FileInputStream(file));
  1536. createParents(ftp, filename);
  1537. ftp.storeFile(resolveFile(filename), instream);
  1538. boolean success = FTPReply.isPositiveCompletion(ftp.getReplyCode());
  1539. if (!success) {
  1540. String s = "could not put file: " + ftp.getReplyString();
  1541. if (skipFailedTransfers) {
  1542. log(s, Project.MSG_WARN);
  1543. skipped++;
  1544. } else {
  1545. throw new BuildException(s);
  1546. }
  1547. } else {
  1548. // see if we should issue a chmod command
  1549. if (chmod != null) {
  1550. doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(filename));
  1551. }
  1552. log("File " + file.getAbsolutePath() + " copied to " + server,
  1553. Project.MSG_VERBOSE);
  1554. transferred++;
  1555. }
  1556. } finally {
  1557. if (instream != null) {
  1558. try {
  1559. instream.close();
  1560. } catch (IOException ex) {
  1561. // ignore it
  1562. }
  1563. }
  1564. }
  1565. }
  1566. /**
  1567. * Delete a file from the remote host.
  1568. * @param ftp ftp client
  1569. * @param filename file to delete
  1570. * @throws IOException in unknown circumstances
  1571. * @throws BuildException if skipFailedTransfers is set to false
  1572. * and the deletion could not be done
  1573. */
  1574. protected void delFile(FTPClient ftp, String filename)
  1575. throws IOException, BuildException {
  1576. if (verbose) {
  1577. log("deleting " + filename);
  1578. }
  1579. if (!ftp.deleteFile(resolveFile(filename))) {
  1580. String s = "could not delete file: " + ftp.getReplyString();
  1581. if (skipFailedTransfers) {
  1582. log(s, Project.MSG_WARN);
  1583. skipped++;
  1584. } else {
  1585. throw new BuildException(s);
  1586. }
  1587. } else {
  1588. log("File " + filename + " deleted from " + server,
  1589. Project.MSG_VERBOSE);
  1590. transferred++;
  1591. }
  1592. }
  1593. /**
  1594. * Delete a directory, if empty, from the remote host.
  1595. * @param ftp ftp client
  1596. * @param dirname directory to delete
  1597. * @throws IOException in unknown circumstances
  1598. * @throws BuildException if skipFailedTransfers is set to false
  1599. * and the deletion could not be done
  1600. */
  1601. protected void rmDir(FTPClient ftp, String dirname)
  1602. throws IOException, BuildException {
  1603. if (verbose) {
  1604. log("removing " + dirname);
  1605. }
  1606. if (!ftp.removeDirectory(resolveFile(dirname))) {
  1607. String s = "could not remove directory: " + ftp.getReplyString();
  1608. if (skipFailedTransfers) {
  1609. log(s, Project.MSG_WARN);
  1610. skipped++;
  1611. } else {
  1612. throw new BuildException(s);
  1613. }
  1614. } else {
  1615. log("Directory " + dirname + " removed from " + server,
  1616. Project.MSG_VERBOSE);
  1617. transferred++;
  1618. }
  1619. }
  1620. /**
  1621. * Retrieve a single file from the remote host. <code>filename</code> may
  1622. * contain a relative path specification. <p>
  1623. *
  1624. * The file will then be retreived using the entire relative path spec -
  1625. * no attempt is made to change directories. It is anticipated that this
  1626. * may eventually cause problems with some FTP servers, but it simplifies
  1627. * the coding.</p>
  1628. * @param ftp the ftp client
  1629. * @param dir local base directory to which the file should go back
  1630. * @param filename relative path of the file based upon the ftp remote directory
  1631. * and/or the local base directory (dir)
  1632. * @throws IOException in unknown circumstances
  1633. * @throws BuildException if skipFailedTransfers is false
  1634. * and the file cannot be retrieved.
  1635. */
  1636. protected void getFile(FTPClient ftp, String dir, String filename)
  1637. throws IOException, BuildException {
  1638. OutputStream outstream = null;
  1639. try {
  1640. File file = getProject().resolveFile(new File(dir, filename).getPath());
  1641. if (newerOnly && isUpToDate(ftp, file, resolveFile(filename))) {
  1642. return;
  1643. }
  1644. if (verbose) {
  1645. log("transferring " + filename + " to "
  1646. + file.getAbsolutePath());
  1647. }
  1648. File pdir = fileUtils.getParentFile(file);
  1649. if (!pdir.exists()) {
  1650. pdir.mkdirs();
  1651. }
  1652. outstream = new BufferedOutputStream(new FileOutputStream(file));
  1653. ftp.retrieveFile(resolveFile(filename), outstream);
  1654. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1655. String s = "could not get file: " + ftp.getReplyString();
  1656. if (skipFailedTransfers) {
  1657. log(s, Project.MSG_WARN);
  1658. skipped++;
  1659. } else {
  1660. throw new BuildException(s);
  1661. }
  1662. } else {
  1663. log("File " + file.getAbsolutePath() + " copied from "
  1664. + server, Project.MSG_VERBOSE);
  1665. transferred++;
  1666. if (preserveLastModified) {
  1667. outstream.close();
  1668. outstream = null;
  1669. FTPFile[] remote = ftp.listFiles(resolveFile(filename));
  1670. if (remote.length > 0) {
  1671. fileUtils.setFileLastModified(file,
  1672. remote[0].getTimestamp()
  1673. .getTime().getTime());
  1674. }
  1675. }
  1676. }
  1677. } finally {
  1678. if (outstream != null) {
  1679. try {
  1680. outstream.close();
  1681. } catch (IOException ex) {
  1682. // ignore it
  1683. }
  1684. }
  1685. }
  1686. }
  1687. /**
  1688. * List information about a single file from the remote host. <code>filename</code>
  1689. * may contain a relative path specification. <p>
  1690. *
  1691. * The file listing will then be retrieved using the entire relative path
  1692. * spec - no attempt is made to change directories. It is anticipated that
  1693. * this may eventually cause problems with some FTP servers, but it
  1694. * simplifies the coding.</p>
  1695. * @param ftp ftp client
  1696. * @param bw buffered writer
  1697. * @param filename the directory one wants to list
  1698. * @throws IOException in unknown circumstances
  1699. * @throws BuildException in unknown circumstances
  1700. */
  1701. protected void listFile(FTPClient ftp, BufferedWriter bw, String filename)
  1702. throws IOException, BuildException {
  1703. if (verbose) {
  1704. log("listing " + filename);
  1705. }
  1706. FTPFile ftpfile = ftp.listFiles(resolveFile(filename))[0];
  1707. bw.write(ftpfile.toString());
  1708. bw.newLine();
  1709. transferred++;
  1710. }
  1711. /**
  1712. * Create the specified directory on the remote host.
  1713. *
  1714. * @param ftp The FTP client connection
  1715. * @param dir The directory to create (format must be correct for host
  1716. * type)
  1717. * @throws IOException in unknown circumstances
  1718. * @throws BuildException if ignoreNoncriticalErrors has not been set to true
  1719. * and a directory could not be created, for instance because it was
  1720. * already existing. Precisely, the codes 521, 550 and 553 will trigger
  1721. * a BuildException
  1722. */
  1723. protected void makeRemoteDir(FTPClient ftp, String dir)
  1724. throws IOException, BuildException {
  1725. String workingDirectory = ftp.printWorkingDirectory();
  1726. if (verbose) {
  1727. log("Creating directory: " + dir);
  1728. }
  1729. if (dir.indexOf("/") == 0) {
  1730. ftp.changeWorkingDirectory("/");
  1731. }
  1732. String subdir = new String();
  1733. StringTokenizer st = new StringTokenizer(dir, "/");
  1734. while (st.hasMoreTokens()) {
  1735. subdir = st.nextToken();
  1736. log("Checking " + subdir, Project.MSG_DEBUG);
  1737. if (!ftp.changeWorkingDirectory(subdir)) {
  1738. if (!ftp.makeDirectory(subdir)) {
  1739. // codes 521, 550 and 553 can be produced by FTP Servers
  1740. // to indicate that an attempt to create a directory has
  1741. // failed because the directory already exists.
  1742. int rc = ftp.getReplyCode();
  1743. if (!(ignoreNoncriticalErrors
  1744. && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553
  1745. || rc == CODE_521))) {
  1746. throw new BuildException("could not create directory: "
  1747. + ftp.getReplyString());
  1748. }
  1749. if (verbose) {
  1750. log("Directory already exists");
  1751. }
  1752. } else {
  1753. if (verbose) {
  1754. log("Directory created OK");
  1755. }
  1756. ftp.changeWorkingDirectory(subdir);
  1757. }
  1758. }
  1759. }
  1760. if (workingDirectory != null) {
  1761. ftp.changeWorkingDirectory(workingDirectory);
  1762. }
  1763. }
  1764. /**
  1765. * look at the response for a failed mkdir action, decide whether
  1766. * it matters or not. If it does, we throw an exception
  1767. * @param ftp current ftp connection
  1768. * @throws BuildException if this is an error to signal
  1769. */
  1770. private void handleMkDirFailure(FTPClient ftp)
  1771. throws BuildException {
  1772. int rc = ftp.getReplyCode();
  1773. if (!(ignoreNoncriticalErrors
  1774. && (rc == FTPReply.CODE_550 || rc == FTPReply.CODE_553 || rc == CODE_521))) {
  1775. throw new BuildException("could not create directory: "
  1776. + ftp.getReplyString());
  1777. }
  1778. }
  1779. /**
  1780. * Runs the task.
  1781. *
  1782. * @throws BuildException if the task fails or is not configured
  1783. * correctly.
  1784. */
  1785. public void execute() throws BuildException {
  1786. checkConfiguration();
  1787. FTPClient ftp = null;
  1788. try {
  1789. log("Opening FTP connection to " + server, Project.MSG_VERBOSE);
  1790. ftp = new FTPClient();
  1791. ftp.connect(server, port);
  1792. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1793. throw new BuildException("FTP connection failed: "
  1794. + ftp.getReplyString());
  1795. }
  1796. log("connected", Project.MSG_VERBOSE);
  1797. log("logging in to FTP server", Project.MSG_VERBOSE);
  1798. if (!ftp.login(userid, password)) {
  1799. throw new BuildException("Could not login to FTP server");
  1800. }
  1801. log("login succeeded", Project.MSG_VERBOSE);
  1802. if (binary) {
  1803. ftp.setFileType(org.apache.commons.net.ftp.FTP.IMAGE_FILE_TYPE);
  1804. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1805. throw new BuildException("could not set transfer type: "
  1806. + ftp.getReplyString());
  1807. }
  1808. } else {
  1809. ftp.setFileType(org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
  1810. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1811. throw new BuildException("could not set transfer type: "
  1812. + ftp.getReplyString());
  1813. }
  1814. }
  1815. if (passive) {
  1816. log("entering passive mode", Project.MSG_VERBOSE);
  1817. ftp.enterLocalPassiveMode();
  1818. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1819. throw new BuildException("could not enter into passive "
  1820. + "mode: " + ftp.getReplyString());
  1821. }
  1822. }
  1823. // For a unix ftp server you can set the default mask for all files
  1824. // created.
  1825. if (umask != null) {
  1826. doSiteCommand(ftp, "umask " + umask);
  1827. }
  1828. // If the action is MK_DIR, then the specified remote
  1829. // directory is the directory to create.
  1830. if (action == MK_DIR) {
  1831. makeRemoteDir(ftp, remotedir);
  1832. } else {
  1833. if (remotedir != null) {
  1834. log("changing the remote directory", Project.MSG_VERBOSE);
  1835. ftp.changeWorkingDirectory(remotedir);
  1836. if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
  1837. throw new BuildException("could not change remote "
  1838. + "directory: " + ftp.getReplyString());
  1839. }
  1840. }
  1841. if (newerOnly && timeDiffAuto) {
  1842. // in this case we want to find how much time span there is between local
  1843. // and remote
  1844. timeDiffMillis = getTimeDiff(ftp);
  1845. }
  1846. log(ACTION_STRS[action] + " " + ACTION_TARGET_STRS[action]);
  1847. transferFiles(ftp);
  1848. }
  1849. } catch (IOException ex) {
  1850. throw new BuildException("error during FTP transfer: " + ex);
  1851. } finally {
  1852. if (ftp != null && ftp.isConnected()) {
  1853. try {
  1854. log("disconnecting", Project.MSG_VERBOSE);
  1855. ftp.logout();
  1856. ftp.disconnect();
  1857. } catch (IOException ex) {
  1858. // ignore it
  1859. }
  1860. }
  1861. }
  1862. }
  1863. /**
  1864. * an action to perform, one of
  1865. * "send", "put", "recv", "get", "del", "delete", "list", "mkdir", "chmod",
  1866. * "rmdir"
  1867. */
  1868. public static class Action extends EnumeratedAttribute {
  1869. private static final String[] VALID_ACTIONS = {
  1870. "send", "put", "recv", "get", "del", "delete", "list", "mkdir",
  1871. "chmod", "rmdir"
  1872. };
  1873. /**
  1874. * Get the valid values
  1875. *
  1876. * @return an array of the valid FTP actions.
  1877. */
  1878. public String[] getValues() {
  1879. return VALID_ACTIONS;
  1880. }
  1881. /**
  1882. * Get the symbolic equivalent of the action value.
  1883. *
  1884. * @return the SYMBOL representing the given action.
  1885. */
  1886. public int getAction() {
  1887. String actionL = getValue().toLowerCase(Locale.US);
  1888. if (actionL.equals("send") || actionL.equals("put")) {
  1889. return SEND_FILES;
  1890. } else if (actionL.equals("recv") || actionL.equals("get")) {
  1891. return GET_FILES;
  1892. } else if (actionL.equals("del") || actionL.equals("delete")) {
  1893. return DEL_FILES;
  1894. } else if (actionL.equals("list")) {
  1895. return LIST_FILES;
  1896. } else if (actionL.equals("chmod")) {
  1897. return CHMOD;
  1898. } else if (actionL.equals("mkdir")) {
  1899. return MK_DIR;
  1900. } else if (actionL.equals("rmdir")) {
  1901. return RM_DIR;
  1902. }
  1903. return SEND_FILES;
  1904. }
  1905. }
  1906. }