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