1 /*
2 * SSHTools - Java SSH2 API
3 *
4 * Copyright (C) 2002-2003 Lee David Painter and Contributors.
5 *
6 * Contributions made by:
7 *
8 * Brett Smith
9 * Richard Pernavas
10 * Erwin Bolwidt
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26 package com.sshtools.j2ssh;
27
28 import com.sshtools.j2ssh.connection;
29 import com.sshtools.j2ssh.io;
30 import com.sshtools.j2ssh.sftp;
31
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileNotFoundException;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.StringTokenizer;
43 import java.util.Vector;
44
45
46 /**
47 * <p>
48 * Implements a Secure File Transfer (SFTP) client.
49 * </p>
50 *
51 * @author Lee David Painter
52 * @version $Revision: 1.44 $
53 *
54 * @since 0.2.0
55 */
56 public class SftpClient {
57 SftpSubsystemClient sftp;
58 String cwd;
59 String lcwd;
60 private int BLOCKSIZE = 65535;
61
62 // Default permissions is determined by default_permissions ^ umask
63 int umask = 0022;
64 int default_permissions = 0777;
65
66 /**
67 * <p>
68 * Constructs the SFTP client.
69 * </p>
70 *
71 * @param ssh the <code>SshClient</code> instance
72 *
73 * @throws IOException if an IO error occurs
74 */
75 SftpClient(SshClient ssh) throws IOException {
76 this(ssh, null);
77 }
78
79 /**
80 * <p>
81 * Constructs the SFTP client with a given channel event listener.
82 * </p>
83 *
84 * @param ssh the <code>SshClient</code> instance
85 * @param eventListener an event listener implementation
86 *
87 * @throws IOException if an IO error occurs
88 */
89 SftpClient(SshClient ssh, ChannelEventListener eventListener)
90 throws IOException {
91 if (!ssh.isConnected()) {
92 throw new IOException("SshClient is not connected");
93 }
94
95 this.sftp = ssh.openSftpChannel(eventListener);
96
97 // Get the users default directory
98 cwd = sftp.getDefaultDirectory();
99 lcwd = System.getProperty("user.home");
100 }
101
102 /**
103 * Sets the umask used by this client.
104 * @param umask
105 * @return the previous umask value
106 */
107 public int umask(int umask) {
108 int old = umask;
109 this.umask = umask;
110
111 return old;
112 }
113
114 /**
115 * <p>
116 * Changes the working directory on the remote server.
117 * </p>
118 *
119 * @param dir the new working directory
120 *
121 * @throws IOException if an IO error occurs or the file does not exist
122 * @throws FileNotFoundException
123 *
124 * @since 0.2.0
125 */
126 public void cd(String dir) throws IOException {
127 try {
128 String actual;
129
130 if (dir.equals("")) {
131 actual = sftp.getDefaultDirectory();
132 } else {
133 actual = resolveRemotePath(dir);
134 actual = sftp.getAbsolutePath(actual);
135 }
136
137 FileAttributes attr = sftp.getAttributes(actual);
138
139 if (!attr.isDirectory()) {
140 throw new IOException(dir + " is not a directory");
141 }
142
143 cwd = actual;
144 } catch (IOException ex) {
145 throw new FileNotFoundException(dir + " could not be found");
146 }
147 }
148
149 private File resolveLocalPath(String path) throws IOException {
150 File f = new File(path);
151
152 if (!f.isAbsolute()) {
153 f = new File(lcwd, path);
154 }
155
156 return f;
157 }
158
159 private String resolveRemotePath(String path) throws IOException {
160 verifyConnection();
161
162 String actual;
163
164 if (!path.startsWith("/")) {
165 actual = cwd + (cwd.endsWith("/") ? "" : "/") + path;
166 } else {
167 actual = path;
168 }
169
170 return actual;
171 }
172
173 private void verifyConnection() throws SshException {
174 if (sftp.isClosed()) {
175 throw new SshException("The SFTP connection has been closed");
176 }
177 }
178
179 /**
180 * <p>
181 * Creates a new directory on the remote server. This method will throw an
182 * exception if the directory already exists. To create directories and
183 * disregard any errors use the <code>mkdirs</code> method.
184 * </p>
185 *
186 * @param dir the name of the new directory
187 *
188 * @throws IOException if an IO error occurs or if the directory already
189 * exists
190 *
191 * @since 0.2.0
192 */
193 public void mkdir(String dir) throws IOException {
194 String actual = resolveRemotePath(dir);
195
196 try {
197 FileAttributes attrs = stat(actual);
198
199 if (!attrs.isDirectory()) {
200 throw new IOException("File already exists named " + dir);
201 }
202 } catch (IOException ex) {
203 sftp.makeDirectory(actual);
204 chmod(default_permissions ^ umask, actual);
205 }
206 }
207
208 /**
209 * <p>
210 * Create a directory or set of directories. This method will not fail even
211 * if the directories exist. It is advisable to test whether the directory
212 * exists before attempting an operation by using the <code>stat</code>
213 * method to return the directories attributes.
214 * </p>
215 *
216 * @param dir the path of directories to create.
217 */
218 public void mkdirs(String dir) {
219 StringTokenizer tokens = new StringTokenizer(dir, "/");
220 String path = dir.startsWith("/") ? "/" : "";
221
222 while (tokens.hasMoreElements()) {
223 path += (String) tokens.nextElement();
224
225 try {
226 stat(path);
227 } catch (IOException ex) {
228 try {
229 mkdir(path);
230 } catch (IOException ex2) {
231 }
232 }
233
234 path += "/";
235 }
236 }
237
238 /**
239 * <p>
240 * Returns the absolute path name of the current remote working directory.
241 * </p>
242 *
243 * @return the absolute path of the remote working directory.
244 *
245 * @since 0.2.0
246 */
247 public String pwd() {
248 return cwd;
249 }
250
251 /**
252 * <p>
253 * List the contents of the current remote working directory.
254 * </p>
255 *
256 * <p>
257 * Returns a list of <code>SftpFile</code> instances for the current
258 * working directory.
259 * </p>
260 *
261 * @return a list of SftpFile for the current working directory
262 *
263 * @throws IOException if an IO error occurs
264 *
265 * @see com.sshtools.j2ssh.sftp.SftpFile
266 * @since 0.2.0
267 */
268 public List ls() throws IOException {
269 return ls(cwd);
270 }
271
272 /**
273 * <p>
274 * List the contents remote directory.
275 * </p>
276 *
277 * <p>
278 * Returns a list of <code>SftpFile</code> instances for the remote
279 * directory.
280 * </p>
281 *
282 * @param path the path on the remote server to list
283 *
284 * @return a list of SftpFile for the remote directory
285 *
286 * @throws IOException if an IO error occurs
287 *
288 * @see com.sshtools.j2ssh.sftp.SftpFile
289 * @since 0.2.0
290 */
291 public List ls(String path) throws IOException {
292 String actual = resolveRemotePath(path);
293 FileAttributes attrs = sftp.getAttributes(actual);
294
295 if (!attrs.isDirectory()) {
296 throw new IOException(path + " is not a directory");
297 }
298
299 SftpFile file = sftp.openDirectory(actual);
300 Vector children = new Vector();
301
302 while (sftp.listChildren(file, children) > -1) {
303 ;
304 }
305
306 file.close();
307
308 return children;
309 }
310
311 /**
312 * <p>
313 * Changes the local working directory.
314 * </p>
315 *
316 * @param path the path to the new working directory
317 *
318 * @throws IOException if an IO error occurs
319 *
320 * @since 0.2.0
321 */
322 public void lcd(String path) throws IOException {
323 File actual;
324
325 if (!isLocalAbsolutePath(path)) {
326 actual = new File(lcwd, path);
327 } else {
328 actual = new File(path);
329 }
330
331 if (!actual.isDirectory()) {
332 throw new IOException(path + " is not a directory");
333 }
334
335 lcwd = actual.getCanonicalPath();
336 }
337
338 private static boolean isLocalAbsolutePath(String path) {
339 return (new File(path)).isAbsolute();
340 }
341
342 /**
343 * <p>
344 * Returns the absolute path to the local working directory.
345 * </p>
346 *
347 * @return the absolute path of the local working directory.
348 *
349 * @since 0.2.0
350 */
351 public String lpwd() {
352 return lcwd;
353 }
354
355 /**
356 * <p>
357 * Download the remote file to the local computer.
358 * </p>
359 *
360 * @param path the path to the remote file
361 * @param progress
362 *
363 * @return
364 *
365 * @throws IOException if an IO error occurs of the file does not exist
366 * @throws TransferCancelledException
367 *
368 * @since 0.2.0
369 */
370 public FileAttributes get(String path, FileTransferProgress progress)
371 throws IOException, TransferCancelledException {
372 String localfile;
373
374 if (path.lastIndexOf("/") > -1) {
375 localfile = path.substring(path.lastIndexOf("/") + 1);
376 } else {
377 localfile = path;
378 }
379
380 return get(path, localfile, progress);
381 }
382
383 /**
384 *
385 *
386 * @param path
387 *
388 * @return
389 *
390 * @throws IOException
391 */
392 public FileAttributes get(String path) throws IOException {
393 return get(path, (FileTransferProgress) null);
394 }
395
396 private void transferFile(InputStream in, OutputStream out)
397 throws IOException, TransferCancelledException {
398 transferFile(in, out, null);
399 }
400
401 private void transferFile(
402 InputStream in,
403 OutputStream out,
404 FileTransferProgress progress)
405 throws IOException, TransferCancelledException
406 {
407 try {
408 long bytesSoFar = 0;
409 byte[] buffer = new byte[BLOCKSIZE];
410 int read;
411
412 while ((read = in.read(buffer)) > -1) {
413 if ((progress != null) && progress.isCancelled()) {
414 throw new TransferCancelledException();
415 }
416
417 if (read > 0) {
418 out.write(buffer, 0, read);
419
420 //out.flush();
421 bytesSoFar += read;
422
423 if (progress != null) {
424 progress.progressed(bytesSoFar);
425 }
426 }
427 }
428 }
429 finally {
430 try {
431 in.close();
432 out.close();
433 }
434 catch (IOException ex) {
435 }
436 }
437 }
438
439 /**
440 * <p>
441 * Download the remote file to the local computer. If the paths provided
442 * are not absolute the current working directory is used.
443 * </p>
444 *
445 * @param remote the path/name of the remote file
446 * @param local the path/name to place the file on the local computer
447 * @param progress
448 *
449 * @return
450 *
451 * @throws IOException if an IO error occurs or the file does not exist
452 * @throws TransferCancelledException
453 *
454 * @since 0.2.0
455 */
456 public FileAttributes get(String remote, String local,
457 FileTransferProgress progress)
458 throws IOException, TransferCancelledException {
459 File localPath = resolveLocalPath(local);
460
461 if (!localPath.exists()) {
462 localPath.getParentFile().mkdirs();
463 localPath.createNewFile();
464 }
465
466 FileOutputStream out = new FileOutputStream(localPath);
467
468 return get(remote, out, progress);
469 }
470
471 /**
472 *
473 *
474 * @param remote
475 * @param local
476 *
477 * @return
478 *
479 * @throws IOException
480 */
481 public FileAttributes get(String remote, String local)
482 throws IOException {
483 return get(remote, local, null);
484 }
485
486 /**
487 * <p>
488 * Download the remote file writing it to the specified
489 * <code>OutputStream</code>. The OutputStream is closed by this mehtod
490 * even if the operation fails.
491 * </p>
492 *
493 * @param remote the path/name of the remote file
494 * @param local the OutputStream to write
495 * @param progress
496 *
497 * @return
498 *
499 * @throws IOException if an IO error occurs or the file does not exist
500 * @throws TransferCancelledException
501 *
502 * @since 0.2.0
503 */
504 public FileAttributes get(String remote, OutputStream local,
505 FileTransferProgress progress)
506 throws IOException, TransferCancelledException {
507 String remotePath = resolveRemotePath(remote);
508 FileAttributes attrs = stat(remotePath);
509
510 if (progress != null) {
511 progress.started(attrs.getSize().longValue(), remotePath);
512 }
513
514 SftpFileInputStream in = new SftpFileInputStream(sftp.openFile(
515 remotePath, SftpSubsystemClient.OPEN_READ));
516 transferFile(in, local, progress);
517
518 if (progress != null) {
519 progress.completed();
520 }
521
522 return attrs;
523 }
524
525 /**
526 *
527 *
528 * @param remote
529 * @param local
530 *
531 * @return
532 *
533 * @throws IOException
534 */
535 public FileAttributes get(String remote, OutputStream local)
536 throws IOException {
537 return get(remote, local, null);
538 }
539
540 /**
541 * <p>
542 * Returns the state of the SFTP client. The client is closed if the
543 * underlying session channel is closed. Invoking the <code>quit</code>
544 * method of this object will close the underlying session channel.
545 * </p>
546 *
547 * @return true if the client is still connected, otherwise false
548 *
549 * @since 0.2.0
550 */
551 public boolean isClosed() {
552 return sftp.isClosed();
553 }
554
555 /**
556 * <p>
557 * Upload a file to the remote computer.
558 * </p>
559 *
560 * @param local the path/name of the local file
561 * @param progress
562 *
563 * @return
564 *
565 * @throws IOException if an IO error occurs or the file does not exist
566 * @throws TransferCancelledException
567 *
568 * @since 0.2.0
569 */
570 public void put(String local, FileTransferProgress progress)
571 throws IOException, TransferCancelledException {
572 File f = new File(local);
573 put(local, f.getName(), progress);
574 }
575
576 /**
577 *
578 *
579 * @param local
580 *
581 * @return
582 *
583 * @throws IOException
584 */
585 public void put(String local) throws IOException {
586 put(local, (FileTransferProgress) null);
587 }
588
589 /**
590 * <p>
591 * Upload a file to the remote computer. If the paths provided are not
592 * absolute the current working directory is used.
593 * </p>
594 *
595 * @param local the path/name of the local file
596 * @param remote the path/name of the destination file
597 * @param progress
598 *
599 * @return
600 *
601 * @throws IOException if an IO error occurs or the file does not exist
602 * @throws TransferCancelledException
603 *
604 * @since 0.2.0
605 */
606 public void put(String local, String remote, FileTransferProgress progress)
607 throws IOException, TransferCancelledException {
608 File localPath = resolveLocalPath(local);
609 FileInputStream in = new FileInputStream(localPath);
610
611 try {
612 FileAttributes attrs = stat(remote);
613
614 if (attrs.isDirectory()) {
615 File f = new File(local);
616 remote += ((remote.endsWith("/") ? "" : "/") + f.getName());
617 }
618 } catch (IOException ex) {
619 }
620
621 put(in, remote, progress);
622 }
623
624 /**
625 *
626 *
627 * @param local
628 * @param remote
629 *
630 * @return
631 *
632 * @throws IOException
633 */
634 public void put(String local, String remote) throws IOException {
635 put(local, remote, null);
636 }
637
638 /**
639 * <p>
640 * Upload a file to the remote computer reading from the specified <code>
641 * InputStream</code>. The InputStream is closed, even if the operation
642 * fails.
643 * </p>
644 *
645 * @param in the InputStream being read
646 * @param remote the path/name of the destination file
647 * @param progress
648 *
649 * @return
650 *
651 * @throws IOException if an IO error occurs
652 * @throws TransferCancelledException
653 *
654 * @since 0.2.0
655 */
656 public void put(InputStream in, String remote, FileTransferProgress progress)
657 throws IOException, TransferCancelledException {
658 String remotePath = resolveRemotePath(remote);
659 SftpFileOutputStream out;
660 FileAttributes attrs;
661 boolean newfile = false;
662
663 try {
664 attrs = stat(remotePath);
665 out = new SftpFileOutputStream(sftp.openFile(remotePath,
666 SftpSubsystemClient.OPEN_CREATE |
667 SftpSubsystemClient.OPEN_TRUNCATE |
668 SftpSubsystemClient.OPEN_WRITE));
669 } catch (IOException ex) {
670 attrs = new FileAttributes();
671 newfile = true;
672 attrs.setPermissions(new UnsignedInteger32(default_permissions ^
673 umask));
674 out = new SftpFileOutputStream(sftp.openFile(remotePath,
675 SftpSubsystemClient.OPEN_CREATE |
676 SftpSubsystemClient.OPEN_WRITE, attrs));
677 }
678
679 if (progress != null) {
680 progress.started(in.available(), remotePath);
681 }
682
683 transferFile(in, out, progress);
684
685 if (progress != null) {
686 progress.completed();
687 }
688
689 // Set the permissions here since at creation they dont always work
690 if (newfile) {
691 chmod(default_permissions ^ umask, remotePath);
692 }
693 }
694
695 /**
696 *
697 *
698 * @param in
699 * @param remote
700 *
701 * @return
702 *
703 * @throws IOException
704 */
705 public void put(InputStream in, String remote) throws IOException {
706 put(in, remote, null);
707 }
708
709 /**
710 * <p>
711 * Sets the user ID to owner for the file or directory.
712 * </p>
713 *
714 * @param uid numeric user id of the new owner
715 * @param path the path to the remote file/directory
716 *
717 * @throws IOException if an IO error occurs or the file does not exist
718 *
719 * @since 0.2.0
720 */
721 public void chown(int uid, String path) throws IOException {
722 String actual = resolveRemotePath(path);
723 FileAttributes attrs = sftp.getAttributes(actual);
724 attrs.setUID(new UnsignedInteger32(uid));
725 sftp.setAttributes(actual, attrs);
726 }
727
728 /**
729 * <p>
730 * Sets the group ID for the file or directory.
731 * </p>
732 *
733 * @param gid the numeric group id for the new group
734 * @param path the path to the remote file/directory
735 *
736 * @throws IOException if an IO error occurs or the file does not exist
737 *
738 * @since 0.2.0
739 */
740 public void chgrp(int gid, String path) throws IOException {
741 String actual = resolveRemotePath(path);
742 FileAttributes attrs = sftp.getAttributes(actual);
743 attrs.setGID(new UnsignedInteger32(gid));
744 sftp.setAttributes(actual, attrs);
745 }
746
747 /**
748 * <p>
749 * Changes the access permissions or modes of the specified file or
750 * directory.
751 * </p>
752 *
753 * <p>
754 * Modes determine who can read, change or execute a file.
755 * </p>
756 * <blockquote><pre>Absolute modes are octal numbers specifying the complete list of
757 * attributes for the files; you specify attributes by OR'ing together
758 * these bits.
759 *
760 * 0400 Individual read
761 * 0200 Individual write
762 * 0100 Individual execute (or list directory)
763 * 0040 Group read
764 * 0020 Group write
765 * 0010 Group execute
766 * 0004 Other read
767 * 0002 Other write
768 * 0001 Other execute </pre></blockquote>
769 *
770 * @param permissions the absolute mode of the file/directory
771 * @param path the path to the file/directory on the remote server
772 *
773 * @throws IOException if an IO error occurs or the file if not found
774 *
775 * @since 0.2.0
776 */
777 public void chmod(int permissions, String path) throws IOException {
778 String actual = resolveRemotePath(path);
779 sftp.changePermissions(actual, permissions);
780 }
781
782 public void umask(String umask) throws IOException {
783 try {
784 this.umask = Integer.parseInt(umask, 8);
785 } catch (NumberFormatException ex) {
786 throw new IOException(
787 "umask must be 4 digit octal number e.g. 0022");
788 }
789 }
790
791 /**
792 * <p>
793 * Rename a file on the remote computer.
794 * </p>
795 *
796 * @param oldpath the old path
797 * @param newpath the new path
798 *
799 * @throws IOException if an IO error occurs
800 *
801 * @since 0.2.0
802 */
803 public void rename(String oldpath, String newpath)
804 throws IOException {
805 String from = resolveRemotePath(oldpath);
806 String to = resolveRemotePath(newpath);
807 sftp.renameFile(from, to);
808 }
809
810 /**
811 * <p>
812 * Remove a file or directory from the remote computer.
813 * </p>
814 *
815 * @param path the path of the remote file/directory
816 *
817 * @throws IOException if an IO error occurs
818 *
819 * @since 0.2.0
820 */
821 public void rm(String path) throws IOException {
822 String actual = resolveRemotePath(path);
823 FileAttributes attrs = sftp.getAttributes(actual);
824
825 if (attrs.isDirectory()) {
826 sftp.removeDirectory(actual);
827 } else {
828 sftp.removeFile(actual);
829 }
830 }
831
832 /**
833 *
834 *
835 * @param path
836 * @param force
837 * @param recurse
838 *
839 * @throws IOException
840 */
841 public void rm(String path, boolean force, boolean recurse)
842 throws IOException {
843 String actual = resolveRemotePath(path);
844 FileAttributes attrs = sftp.getAttributes(actual);
845 SftpFile file;
846
847 if (attrs.isDirectory()) {
848 List list = ls(path);
849
850 if (!force && (list.size() > 0)) {
851 throw new IOException(
852 "You cannot delete non-empty directory, use force=true to overide");
853 } else {
854 for (Iterator it = list.iterator(); it.hasNext();) {
855 file = (SftpFile) it.next();
856
857 if (file.isDirectory() && !file.getFilename().equals(".") &&
858 !file.getFilename().equals("..")) {
859 if (recurse) {
860 rm(file.getAbsolutePath(), force, recurse);
861 } else {
862 throw new IOException(
863 "Directory has contents, cannot delete without recurse=true");
864 }
865 } else if (file.isFile()) {
866 sftp.removeFile(file.getAbsolutePath());
867 }
868 }
869 }
870
871 sftp.removeDirectory(actual);
872 } else {
873 sftp.removeFile(actual);
874 }
875 }
876
877 /**
878 * <p>
879 * Create a symbolic link on the remote computer.
880 * </p>
881 *
882 * @param path the path to the existing file
883 * @param link the new link
884 *
885 * @throws IOException if an IO error occurs or the operation is not
886 * supported on the remote platform
887 *
888 * @since 0.2.0
889 */
890 public void symlink(String path, String link) throws IOException {
891 String actualPath = resolveRemotePath(path);
892 String actualLink = resolveRemotePath(link);
893 sftp.createSymbolicLink(actualPath, actualLink);
894 }
895
896 /**
897 * <p>
898 * Returns the attributes of the file from the remote computer.
899 * </p>
900 *
901 * @param path the path of the file on the remote computer
902 *
903 * @return the attributes
904 *
905 * @throws IOException if an IO error occurs or the file does not exist
906 *
907 * @see com.sshtools.j2ssh.sftp.FileAttributes
908 * @since 0.2.0
909 */
910 public FileAttributes stat(String path) throws IOException {
911 String actual = resolveRemotePath(path);
912
913 return sftp.getAttributes(actual);
914 }
915
916 /**
917 *
918 *
919 * @param path
920 *
921 * @return
922 *
923 * @throws IOException
924 */
925 public String getAbsolutePath(String path) throws IOException {
926 String actual = resolveRemotePath(path);
927
928 return sftp.getAbsolutePath(path);
929 }
930
931 /**
932 * <p>
933 * Close the SFTP client.
934 * </p>
935 *
936 * @throws IOException
937 *
938 * @since 0.2.0
939 */
940 public void quit() throws IOException {
941 sftp.close();
942 }
943
944 /**
945 *
946 *
947 * @param localdir
948 * @param remotedir
949 * @param recurse
950 * @param sync
951 * @param commit
952 * @param progress
953 *
954 * @return
955 *
956 * @throws IOException
957 */
958 public DirectoryOperation copyLocalDirectory(String localdir,
959 String remotedir, boolean recurse, boolean sync, boolean commit,
960 FileTransferProgress progress) throws IOException {
961 DirectoryOperation op = new DirectoryOperation();
962
963 // Record the previous
964 String pwd = pwd();
965 String lpwd = lpwd();
966 File local = resolveLocalPath(localdir);
967 remotedir = resolveRemotePath(remotedir);
968 remotedir += (remotedir.endsWith("/") ? "" : "/");
969 remotedir += local.getName();
970 remotedir += (remotedir.endsWith("/") ? "" : "/");
971
972 // Setup the remote directory if were committing
973 if (commit) {
974 try {
975 FileAttributes attrs = stat(remotedir);
976 } catch (IOException ex) {
977 mkdir(remotedir);
978 }
979 }
980
981 // List the local files and verify against the remote server
982 File[] ls = local.listFiles();
983
984 if (ls != null) {
985 for (int i = 0; i < ls.length; i++) {
986 if (ls[i].isDirectory() && !ls[i].getName().equals(".") &&
987 !ls[i].getName().equals("..")) {
988 if (recurse) {
989 File f = new File(local, ls[i].getName());
990 op.addDirectoryOperation(copyLocalDirectory(
991 f.getAbsolutePath(), remotedir, recurse, sync,
992 commit, progress), f);
993 }
994 } else if (ls[i].isFile()) {
995 try {
996 FileAttributes attrs = stat(remotedir +
997 ls[i].getName());
998
999 if ((ls[i].length() == attrs.getSize().longValue()) &&
1000 ((ls[i].lastModified() / 1000) == attrs.getModifiedTime()
1001 .longValue())) {
1002 op.addUnchangedFile(ls[i]);
1003 } else {
1004 op.addUpdatedFile(ls[i]);
1005 }
1006 } catch (IOException ex1) {
1007 op.addNewFile(ls[i]);
1008 }
1009
1010 if (commit) {
1011 put(ls[i].getAbsolutePath(),
1012 remotedir + ls[i].getName(), progress);
1013
1014 FileAttributes attrs = stat(remotedir +
1015 ls[i].getName());
1016 attrs.setTimes(new UnsignedInteger32(
1017 ls[i].lastModified() / 1000),
1018 new UnsignedInteger32(ls[i].lastModified() / 1000));
1019 sftp.setAttributes(remotedir + ls[i].getName(), attrs);
1020 }
1021 }
1022 }
1023 }
1024
1025 if (sync) {
1026 // List the contents of the new local directory and remove any
1027 // files/directories that were not updated
1028 try {
1029 List files = ls(remotedir);
1030 SftpFile file;
1031 File f;
1032
1033 for (Iterator it = files.iterator(); it.hasNext();) {
1034 file = (SftpFile) it.next();
1035
1036 // Create a local file object to test for its existence
1037 f = new File(local, file.getFilename());
1038
1039 if (!op.containsFile(file) &&
1040 !file.getFilename().equals(".") &&
1041 !file.getFilename().equals("..")) {
1042 op.addDeletedFile(file);
1043
1044 if (commit) {
1045 if (file.isDirectory()) {
1046 // Recurse through the directory, deleting stuff
1047 recurseMarkForDeletion(file, op);
1048
1049 if (commit) {
1050 rm(file.getAbsolutePath(), true, true);
1051 }
1052 } else if (file.isFile()) {
1053 rm(file.getAbsolutePath());
1054 }
1055 }
1056 }
1057 }
1058 } catch (IOException ex2) {
1059 // Ignorew since if it does not exist we cant delete it
1060 }
1061 }
1062
1063 // Return the operation details
1064 return op;
1065 }
1066
1067 /**
1068 *
1069 *
1070 * @param eventListener
1071 */
1072 public void addEventListener(ChannelEventListener eventListener) {
1073 sftp.addEventListener(eventListener);
1074 }
1075
1076 private void recurseMarkForDeletion(SftpFile file, DirectoryOperation op)
1077 throws IOException {
1078 List list = ls(file.getAbsolutePath());
1079 op.addDeletedFile(file);
1080
1081 for (Iterator it = list.iterator(); it.hasNext();) {
1082 file = (SftpFile) it.next();
1083
1084 if (file.isDirectory() && !file.getFilename().equals(".") &&
1085 !file.getFilename().equals("..")) {
1086 recurseMarkForDeletion(file, op);
1087 } else if (file.isFile()) {
1088 op.addDeletedFile(file);
1089 }
1090 }
1091 }
1092
1093 private void recurseMarkForDeletion(File file, DirectoryOperation op)
1094 throws IOException {
1095 File[] list = file.listFiles();
1096 op.addDeletedFile(file);
1097
1098 if (list != null) {
1099 for (int i = 0; i < list.length; i++) {
1100 file = list[i];
1101
1102 if (file.isDirectory() && !file.getName().equals(".") &&
1103 !file.getName().equals("..")) {
1104 recurseMarkForDeletion(file, op);
1105 } else if (file.isFile()) {
1106 op.addDeletedFile(file);
1107 }
1108 }
1109 }
1110 }
1111
1112 /**
1113 *
1114 *
1115 * @param remotedir
1116 * @param localdir
1117 * @param recurse
1118 * @param sync
1119 * @param commit
1120 * @param progress
1121 *
1122 * @return
1123 *
1124 * @throws IOException
1125 */
1126 public DirectoryOperation copyRemoteDirectory(String remotedir,
1127 String localdir, boolean recurse, boolean sync, boolean commit,
1128 FileTransferProgress progress) throws IOException {
1129 // Create an operation object to hold the information
1130 DirectoryOperation op = new DirectoryOperation();
1131
1132 // Record the previous working directoies
1133 String pwd = pwd();
1134 String lpwd = lpwd();
1135 cd(remotedir);
1136
1137 // Setup the local cwd
1138 String base = remotedir;
1139 int idx = base.lastIndexOf('/');
1140
1141 if (idx != -1) {
1142 base = base.substring(idx + 1);
1143 }
1144
1145 File local = new File(localdir, base);
1146
1147 // File local = new File(localdir, remotedir);
1148 if (!local.isAbsolute()) {
1149 local = new File(lpwd(), localdir);
1150 }
1151
1152 if (!local.exists() && commit) {
1153 local.mkdir();
1154 }
1155
1156 List files = ls();
1157 SftpFile file;
1158 File f;
1159
1160 for (Iterator it = files.iterator(); it.hasNext();) {
1161 file = (SftpFile) it.next();
1162
1163 if (file.isDirectory() && !file.getFilename().equals(".") &&
1164 !file.getFilename().equals("..")) {
1165 if (recurse) {
1166 f = new File(local, file.getFilename());
1167 op.addDirectoryOperation(copyRemoteDirectory(
1168 file.getFilename(), local.getAbsolutePath(),
1169 recurse, sync, commit, progress), f);
1170 }
1171 } else if (file.isFile()) {
1172 f = new File(local, file.getFilename());
1173
1174 if (f.exists() &&
1175 (f.length() == file.getAttributes().getSize().longValue()) &&
1176 ((f.lastModified() / 1000) == file.getAttributes()
1177 .getModifiedTime()
1178 .longValue())) {
1179 if (commit) {
1180 op.addUnchangedFile(f);
1181 } else {
1182 op.addUnchangedFile(file);
1183 }
1184
1185 continue;
1186 }
1187
1188 if (f.exists()) {
1189 if (commit) {
1190 op.addUpdatedFile(f);
1191 } else {
1192 op.addUpdatedFile(file);
1193 }
1194 } else {
1195 if (commit) {
1196 op.addNewFile(f);
1197 } else {
1198 op.addNewFile(file);
1199 }
1200 }
1201
1202 if (commit) {
1203 FileAttributes attrs = get(file.getFilename(),
1204 f.getAbsolutePath(), progress);
1205 f.setLastModified(attrs.getModifiedTime().longValue() * 1000);
1206 }
1207 }
1208 }
1209
1210 if (sync) {
1211 // List the contents of the new local directory and remove any
1212 // files/directories that were not updated
1213 File[] contents = local.listFiles();
1214
1215 if (contents != null) {
1216 for (int i = 0; i < contents.length; i++) {
1217 if (!op.containsFile(contents[i])) {
1218 op.addDeletedFile(contents[i]);
1219
1220 if (contents[i].isDirectory() &&
1221 !contents[i].getName().equals(".") &&
1222 !contents[i].getName().equals("..")) {
1223 recurseMarkForDeletion(contents[i], op);
1224
1225 if (commit) {
1226 IOUtil.recurseDeleteDirectory(contents[i]);
1227 }
1228 } else if (commit) {
1229 contents[i].delete();
1230 }
1231 }
1232 }
1233 }
1234 }
1235
1236 cd(pwd);
1237
1238 return op;
1239 }
1240 }