root/branches/add_stat_caching/ftpsync-0.1/ftpsync.py

Revision 545, 6.8 kB (checked in by schwa, 2 years ago)
Code contributed by Martin Wilck. (Thank you!) I might use some of
this code to add caching of stat results to ftputil.
  • Property svn:eol-style set to native
Line 
1 import getopt
2 import netrc   # for password retrieval from .netrc
3 import os
4 import sys
5 import termios # for getpass
6 import time
7 import traceback
8
9 import loggingclass
10
11 from caching_ftp import CachingFTPHost
12 from rsyncmatch import GlobChain
13 from sync import Synchronizer, RsyncSynchronizer
14
15 # Copied from Python library manual (termios)
16 def getpass(prompt = "Password: "):
17     fd = sys.stdin.fileno()
18     old = termios.tcgetattr(fd)
19     new = termios.tcgetattr(fd)
20     new[3] = new[3] & ~termios.ECHO          # lflags
21     try:
22         termios.tcsetattr(fd, termios.TCSADRAIN, new)
23         passwd = raw_input(prompt)
24     finally:
25         termios.tcsetattr(fd, termios.TCSADRAIN, old)
26     return passwd
27
28
29 def login_data(host):
30     """
31     Derive login data for different FTP host formats:
32     
33     "hostname": check .netrc file, try anonymous otherwise
34     "user:pass@hostname": parse
35     "user@hostname": parse and ask for password interactively
36     """
37
38     user = ""
39     acct = ""
40     passwd = ""
41
42     at = host.find("@")
43     if (at == -1):
44         try:
45             nrc = netrc.netrc()
46             (user, acct, passwd) = \
47                    nrc.authenticators(host)
48         except (IOError, TypeError): # no netrc file or no entry in netrc
49             pass
50     else:
51         user = host[:at]
52         host = host[(at+1):]
53         col = user.find(":")
54         if (col == -1):
55             passwd = getpass("password for ftp://%s%s: " % (user, host))
56         else:
57             passwd = user[(col+1):]
58             user = user[:col]
59
60     if (user == ""):
61         user = "anonymous"
62            
63     return (host, user, passwd, acct)
64
65 def init_ftp(host, dir, **kw):
66     """
67     Set up an FTP session for synchronizing (upload).
68     We must have write permissions in this case.
69     """
70     data = login_data(host)
71     ftp = CachingFTPHost(*data, **kw)
72     ftp.chdir(dir)
73     ftp.synchronize_times()
74     ftp.check_case_insensitive()
75     return ftp
76
77
78 def start_logging(level, logfile):
79     """
80     Initialize a useful logging environment with logging to stderr,
81     slightly more verbose to a log file, and suitable log levels.
82     """
83     loggingclass.init_logging(level)
84    
85     if level == loggingclass.DEBUG:
86 #        loggingclass.getLogger().setLevel(loggingclass.DEBUG)
87         loggingclass.set_default_level(loggingclass.INFO)
88         if logfile != "":
89             loggingclass.init_logfile(logfile, level=loggingclass.DEBUG)
90         loggingclass.set_class_level(RsyncSynchronizer, loggingclass.DEBUG)
91         loggingclass.set_class_level(GlobChain, loggingclass.DEBUG)
92         loggingclass.set_class_level(CachingFTPHost, loggingclass.INFO)
93     else:
94         if logfile != "":
95             loggingclass.init_logfile(logfile, level=loggingclass.INFO)
96         loggingclass.set_class_level(RsyncSynchronizer, loggingclass.INFO)
97         loggingclass.set_class_level(CachingFTPHost, loggingclass.INFO)
98
99 def print_err():
100     err = sys.exc_info()
101     printit = True
102     try:
103         if parm.level != loggingclass.DEBUG:
104             printit = False
105     except:
106         pass
107     if printit:
108         traceback.print_tb(err[2])
109     sys.stderr.write("%s: %s\n" % (err[0], err[1]))
110
111 class _Params:
112     """
113     Class representing options and arguments for do_sync().
114     """
115
116     class UsageError(Exception):
117         pass
118    
119     known = (list(GlobChain().options())
120              + ["delete", "delete-excluded", "dry-run",
121                 "verbose", "quiet", "debug", "trace=",
122                 "cache-expire=", "cache-size="])
123
124     def usage(self):
125         sys.stderr.write("""\
126 Usage: %s [options] host source-dir target-dir
127 Known options: %s
128 """ % (sys.argv[0], ", ".join(["--" + x for x in self.known])))
129         raise self.UsageError()
130
131     def _setlevel(self, level, option=True):
132         if self.level == -1:
133             self.level = level
134         elif option:
135             sys.stderr.write("Only one of --quiet, --debug, --verbose may be specified\n")
136             self.usage()
137
138     def __init__(self):
139         self.level = -1
140         self.dry_run = False
141         self.delete = False
142         self.delete_excluded = False
143         self.logfile = ""
144         self.expire = 300
145         self.size = 2000
146        
147         try:
148             (opts, args) = getopt.gnu_getopt(sys.argv[1:], "", self.known)
149         except getopt.GetoptError:
150             print_err()
151             self.usage()
152
153         for (o, v) in opts:
154             if o == "--delete":
155                 self.delete = True
156             elif o == "--delete-excluded":
157                 self.delete_excluded = True
158             elif o == "--dry-run":
159                 self.dry_run = True
160             elif o == "--verbose":
161                 self._setlevel(loggingclass.INFO)
162             elif o == "--debug":
163                 self._setlevel(loggingclass.DEBUG)
164             elif o == "--quiet":
165                 self._setlevel(loggingclass.WARNING)
166             elif o == "--trace":
167                 self.logfile=v
168             elif o == "--cache-expire":
169                 self.expire = int(v)
170             elif o == "--cache-size":
171                 self.size = int(v)
172
173         self._setlevel(loggingclass.NOTICE, option=False)
174         if len(args) != 3:
175             self.usage()
176            
177         (self.host, self.source, self.target) = args
178         self.opts = opts
179
180
181 def _fix_source_n_target(parm):
182     # rsync-like semantics for trailing slash in source dir
183     if not os.path.isdir(parm.source):
184         raise ValueError, "%s is not a directrory" % parm.source
185    
186     if parm.source.endswith(os.sep):
187         parm.source = parm.source.rstrip(os.sep)
188     else:
189         parm.target = parm.ftp.path.join(parm.target,
190                                          os.path.basename(parm.source))
191         if not parm.ftp.path.exists(parm.target):
192             parm.ftp.mkdir(parm.target)
193
194 def log_checkpoint(msg):
195     loggingclass.getLogger().info(
196         "%s %s %s" % (__file__, msg,
197                       time.strftime("%Y-%m-%d, %H:%M", time.localtime())))
198    
199 def do_sync(parm):
200    
201     start_logging(parm.level, parm.logfile)
202     log_checkpoint("starting at")
203
204     if parm.host == "localhost":
205         parm.ftp = os
206     else:
207         parm.ftp = init_ftp(parm.host, parm.target,
208                             expire=parm.expire, size=parm.size)
209
210     _fix_source_n_target(parm)
211
212     sync = RsyncSynchronizer(os, parm.ftp, parm.source, parm.target,
213                              delete = parm.delete,
214                              dry_run = parm.dry_run,
215                              delete_excluded = parm.delete_excluded)
216    
217     sync.globchain.getopt(parm.opts)
218     sync.sync("")
219    
220     log_checkpoint("finished at")
221
222 if __name__ == "__main__":
223     try:
224         parm = _Params()
225         do_sync(parm)
226     except KeyboardInterrupt:
227         sys.stderr.write("Interrupted.\n")
228         sys.exit(0)
229     except _Params.UsageError:
230         sys.exit(129)
231     except:
232         print_err()
233         sys.exit(130)
234     else:
235         sys.exit(0)
Note: See TracBrowser for help on using the browser.