1 #! /ufs/guido/bin/sgi/python
3 # Mirror a remote ftp subtree into a local directory tree.
4 # Basic usage: ftpmirror [options] host remotedir localdir
7 # - handle symbolic links
8 # - back up .mirrorinfo before overwriting
9 # - use pickles for .mirrorinfo?
17 from fnmatch
import fnmatch
20 usage: ftpmirror [-v] [-q] [-i] [-m] [-n] [-r] [-s pat]
21 [-l username [-p passwd [-a account]]]
22 hostname [remotedir [localdir]]
26 -m: macintosh server (NCSA telnet 2.4) (implies -n -s '*.o')
28 -r: remove files no longer pertinent
29 -l username [-p passwd [-a account]]: login info (default anonymous ftp)
30 -s pat: skip files matching pattern
32 remotedir: remote directory (default initial)
33 localdir: local directory (default current)
36 sys
.stdout
= sys
.stderr
37 for msg
in args
: print msg
41 verbose
= 1 # 0 for -q, 2 for -v
46 skippats
= ['.', '..', '.mirrorinfo']
49 global verbose
, interactive
, mac
, rmok
, nologin
51 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'a:bil:mnp:qrs:v')
52 except getopt
.error
, msg
:
58 if o
== '-l': login
= a
59 if o
== '-p': passwd
= a
60 if o
== '-a': account
= a
61 if o
== '-v': verbose
= verbose
+ 1
62 if o
== '-q': verbose
= 0
63 if o
== '-i': interactive
= 1
64 if o
== '-m': mac
= 1; nologin
= 1; skippats
.append('*.o')
65 if o
== '-n': nologin
= 1
66 if o
== '-r': rmok
= 1
67 if o
== '-s': skippats
.append(a
)
68 if not args
: usage('hostname missing')
76 if args
[3:]: usage('too many arguments')
79 if verbose
: print 'Connecting to %s...' % host
83 print 'Logging in as %s...' % (login
or 'anonymous')
84 f
.login(login
, passwd
, account
)
85 if verbose
: print 'OK.'
87 if verbose
> 1: print 'PWD =', `pwd`
89 if verbose
> 1: print 'cwd(%s)' % `remotedir`
91 if verbose
> 1: print 'OK.'
93 if verbose
> 1: print 'PWD =', `pwd`
95 mirrorsubdir(f
, localdir
)
97 def mirrorsubdir(f
, localdir
):
99 if localdir
and not os
.path
.isdir(localdir
):
100 if verbose
: print 'Creating local directory', localdir
102 infofilename
= os
.path
.join(localdir
, '.mirrorinfo')
104 text
= open(infofilename
, 'r').read()
109 except (SyntaxError, NameError):
110 print 'Bad mirror info in %s' % infofilename
114 if verbose
: print 'Listing remote directory %s...' % pwd
115 f
.retrlines('LIST', listing
.append
)
117 if verbose
> 1: print '-->', `line`
119 # Mac listing has just filenames;
120 # trailing / means subdirectory
121 filename
= string
.strip(line
)
123 if filename
[-1:] == '/':
124 filename
= filename
[:-1]
128 # Parse, assuming a UNIX listing
129 words
= string
.split(line
)
131 if verbose
> 1: print 'Skipping short line'
133 if words
[-2] == '->':
135 print 'Skipping symbolic link %s -> %s' % \
136 (words
[-3], words
[-1])
139 infostuff
= words
[-5:-1]
143 if fnmatch(filename
, pat
):
145 print 'Skip pattern', pat
,
146 print 'matches', filename
153 print 'Remembering subdirectory', filename
154 subdirs
.append(filename
)
156 if info
.has_key(filename
) and info
[filename
] == infostuff
:
158 print 'Already have this version of', filename
160 fullname
= os
.path
.join(localdir
, filename
)
162 doit
= askabout('file', filename
, pwd
)
164 if not info
.has_key(filename
):
165 info
[filename
] = 'Not retrieved'
172 fp
= open(fullname
, 'w')
174 print "Can't create %s: %s" % (fullname
, str(msg
))
177 print 'Retrieving %s from %s as %s...' % \
178 (filename
, pwd
, fullname
)
180 fp1
= LoggingFile(fp
, 1024, sys
.stdout
)
185 f
.retrbinary('RETR ' + filename
, fp1
.write
, 8*1024)
186 except ftplib
.error_perm
, msg
:
193 info
[filename
] = infostuff
194 writedict(info
, infofilename
)
197 kbytes
= bytes
/ 1024.0
198 print int(round(kbytes
)),
200 print int(round(dt
)),
203 print '(~%d Kbytes/sec)' % \
204 int(round(kbytes
/dt
),)
207 # Remove local files that are no longer in the remote directory
208 if not localdir
: names
= os
.listdir(os
.curdir
)
209 else: names
= os
.listdir(localdir
)
211 if name
[0] == '.' or info
.has_key(name
) or name
in subdirs
:
213 fullname
= os
.path
.join(localdir
, name
)
216 print 'Local file', fullname
,
217 print 'is no longer pertinent'
219 if verbose
: print 'Removing local file', fullname
222 except os
.error
, msg
:
223 print "Can't remove local file %s: %s" % \
226 # Recursively mirror subdirectories
227 for subdir
in subdirs
:
229 doit
= askabout('subdirectory', subdir
, pwd
)
230 if not doit
: continue
231 if verbose
: print 'Processing subdirectory', subdir
232 localsubdir
= os
.path
.join(localdir
, subdir
)
235 print 'Remote directory now:', pwd
236 print 'Remote cwd', subdir
239 except ftplib
.error_perm
, msg
:
240 print "Can't chdir to", subdir
, ":", msg
242 if verbose
: print 'Mirroring as', localsubdir
243 mirrorsubdir(f
, localsubdir
)
244 if verbose
> 1: print 'Remote cwd ..'
248 print 'Ended up in wrong directory after cd + cd ..'
249 print 'Giving up now.'
252 if verbose
> 1: print 'OK.'
254 # Wrapper around a file for writing to write a hash sign every block.
256 def __init__(self
, fp
, blocksize
, outfp
):
260 self
.blocksize
= blocksize
262 def write(self
, data
):
263 self
.bytes
= self
.bytes
+ len(data
)
264 hashes
= int(self
.bytes
) / self
.blocksize
265 while hashes
> self
.hashes
:
266 self
.outfp
.write('#')
268 self
.hashes
= self
.hashes
+ 1
271 self
.outfp
.write('\n')
273 # Ask permission to download a file.
274 def askabout(filetype
, filename
, pwd
):
275 prompt
= 'Retrieve %s %s from %s ? [ny] ' % (filetype
, filename
, pwd
)
277 reply
= string
.lower(string
.strip(raw_input(prompt
)))
278 if reply
in ['y', 'ye', 'yes']:
280 if reply
in ['', 'n', 'no', 'nop', 'nope']:
282 print 'Please answer yes or no.'
284 # Create a directory if it doesn't exist. Recursively create the
285 # parent directory as well if needed.
286 def makedir(pathname
):
287 if os
.path
.isdir(pathname
):
289 dirname
= os
.path
.dirname(pathname
)
290 if dirname
: makedir(dirname
)
291 os
.mkdir(pathname
, 0777)
293 # Write a dictionary to a file in a way that can be read back using
294 # rval() but is still somewhat readable (i.e. not a single long line).
295 def writedict(dict, filename
):
296 fp
= open(filename
, 'w')
298 for key
, value
in dict.items():
299 fp
.write('%s: %s,\n' % (`key`
, `value`
))