#!/usr/bin/python # mirrorto.py: # Hacked version of ftpmirror.py that mirrors a local directory tree to # a remote site via ftp. # # Adapted by Chris Lawrence ; may be freely used under # the Python license. # # XXX To do: # - handle symbolic links # - back up .mirrorinfo before overwriting # - use pickles for .mirrorinfo? import os import sys import time import getopt import string import ftplib from fnmatch import fnmatch usage_msg = """ usage: mirrorto [-v] [-q] [-i] [-m] [-n] [-r] [-s pat] [-l username [-p passwd [-a account]]] hostname [remotedir [localdir]] -v: verbose -q: quiet -i: interactive mode -m: macintosh server (NCSA telnet 2.4) (implies -n -s '*.o') -n: don't log in -r: remove files no longer pertinent -l username [-p passwd [-a account]]: login info (default anonymous ftp) -s pat: skip files matching pattern hostname: remote host remotedir: remote directory (default initial) localdir: local directory (default current) """ def usage(*args): sys.stdout = sys.stderr for msg in args: print msg print usage_msg sys.exit(2) verbose = 1 # 0 for -q, 2 for -v interactive = 0 mac = 0 rmok = 0 nologin = 0 skippats = ['.', '..', '.mirrorinfo'] def main(): global verbose, interactive, mac, rmok, nologin try: opts, args = getopt.getopt(sys.argv[1:], 'a:bil:mnp:qrs:v') except getopt.error, msg: usage(msg) login = '' passwd = '' account = '' for o, a in opts: if o == '-l': login = a if o == '-p': passwd = a if o == '-a': account = a if o == '-v': verbose = verbose + 1 if o == '-q': verbose = 0 if o == '-i': interactive = 1 if o == '-m': mac = 1; nologin = 1; skippats.append('*.o') if o == '-n': nologin = 1 if o == '-r': rmok = 1 if o == '-s': skippats.append(a) if not args: usage('hostname missing') host = args[0] remotedir = '' localdir = '' if args[1:]: remotedir = args[1] if args[2:]: localdir = args[2] if args[3:]: usage('too many arguments') # f = ftplib.FTP() if verbose: print 'Connecting to %s...' % host f.connect(host) f.set_pasv(1) if not nologin: if verbose: print 'Logging in as %s...' % (login or 'anonymous') f.login(login, passwd, account) if verbose: print 'OK.' pwd = f.pwd() if verbose > 1: print 'PWD =', `pwd` # mirrorsubdir(f, localdir, remotedir) def mirrorsubdir(f, localdir, remotedir): pwd = f.pwd() mkremdir(f, remotedir) f.cwd(remotedir) infofilename = os.path.join(remotedir, '.mirrorinfo') try: textlines = [] f.retrlines('RETR '+infofilename, textlines.append) text = string.join(textlines, '\n') except ftplib.all_errors, msg: text = '{}' try: info = eval(text) except (SyntaxError, NameError): print 'Bad mirror info in %s' % infofilename info = {} subdirs = [] listing = [] os.chdir(localdir) if verbose: print 'Listing local directory %s...' % localdir fp = os.popen('ls -l '+localdir); listing = map( (lambda x: x[:-1]) , fp.readlines()) for line in listing: if verbose > 1: print '-->', `line` if mac: # Mac listing has just filenames; # trailing / means subdirectory filename = string.strip(line) mode = '-' if filename[-1:] == '/': filename = filename[:-1] mode = 'd' infostuff = '' else: # Parse, assuming a UNIX listing words = string.split(line) if len(words) < 6: if verbose > 1: print 'Skipping short line' continue if words[-2] == '->': if verbose > 1: print 'Skipping symbolic link %s -> %s' % \ (words[-3], words[-1]) continue filename = words[-1] infostuff = words[-5:-1] mode = words[0] skip = 0 for pat in skippats: if fnmatch(filename, pat): if verbose > 1: print 'Skip pattern', pat, print 'matches', filename skip = 1 break if skip: continue if mode[0] == 'd': if verbose > 1: print 'Remembering subdirectory', filename subdirs.append(filename) continue if info.has_key(filename) and info[filename] == infostuff: if verbose > 1: print 'Already have this version of', filename continue fullname = os.path.join(remotedir, filename) tempname = os.path.join(remotedir, '#'+filename) if interactive: doit = askabout('file', filename, pwd) if not doit: if not info.has_key(filename): info[filename] = 'Not put' continue try: f.delete(tempname) except (ftplib.error_perm, ftplib.error_reply): pass try: fp = open(filename, 'r') except IOError, msg: print "Can't open %s: %s" % (filename, str(msg)) continue if verbose: print 'Putting %s as %s...' % (filename, fullname) if verbose: fp1 = LoggingFile(fp, 1024, sys.stdout) else: fp1 = fp t0 = time.time() try: f.storbinary('STOR ' + tempname, fp1, 8*1024) except ftplib.error_perm, msg: print msg t1 = time.time() bytes = fp.tell() fp.close() if fp1 != fp: fp1.close() try: f.delete(fullname) except: pass try: f.rename(tempname, fullname) except ftplib.all_errors, msg: print "Can't rename %s to %s: %s" % (tempname, fullname, str(msg)) continue info[filename] = infostuff writedict(info, f, infofilename) # Attempt to make the file u=rw,go=r try: f.voidcmd("SITE CHMOD 0644 "+fullname); except ftplib.all_errors, msg: print "Can't chmod %s to 0644: %s" % (fullname, str(msg)) if verbose: dt = t1 - t0 kbytes = bytes / 1024.0 print int(round(kbytes)), print 'Kbytes in', print int(round(dt)), print 'seconds', if t1 > t0: print '(~%d Kbytes/sec)' % \ int(round(kbytes/dt),) print # # Remove remote files that are no longer in the local directory try: names = f.nlst(remotedir) names = map(os.path.basename, names) except ftplib.all_errors: names = [] for name in names: localname = os.path.join(localdir, name) if name[0] == '.' or name in subdirs: continue if os.path.exists(localname): dnc = 0 # Remove anything there that's in our skippats for pat in skippats: if fnmatch(name, pat): dnc = 1 if not dnc: continue fullname = os.path.join(remotedir, name) if not rmok: if verbose: print 'Remote file', fullname, print 'is no longer pertinent' continue if verbose: print 'Removing remote file', fullname try: f.delete(fullname) except (ftplib.error_perm), msg: print "Can't remove remote file %s: %s" % \ (fullname, str(msg)) # # Recursively mirror subdirectories for subdir in subdirs: if interactive: doit = askabout('subdirectory', subdir, pwd) if not doit: continue if verbose: print 'Processing subdirectory', subdir localsubdir = os.path.join(localdir, subdir) remotesubdir = os.path.join(remotedir, subdir) pwd = f.pwd() if verbose: print 'Mirroring from', localsubdir mirrorsubdir(f, localsubdir, remotesubdir) if verbose > 1: print 'Remote cwd', remotedir f.cwd(remotedir) newpwd = f.pwd() if newpwd != pwd: print 'Ended up in wrong directory after cd + cd ..' print 'Giving up now.' break else: if verbose > 1: print 'OK.' # Wrapper around a file for writing to write a hash sign every block. class LoggingFile: def __init__(self, fp, blocksize, outfp): self.fp = fp self.bytes = 0 self.hashes = 0 self.blocksize = blocksize self.outfp = outfp def write(self, data): self.bytes = self.bytes + len(data) hashes = int(self.bytes) / self.blocksize while hashes > self.hashes: self.outfp.write('#') self.outfp.flush() self.hashes = self.hashes + 1 self.fp.write(data) def read(self, blocksize=None): if blocksize: data = self.fp.read(blocksize) else: data = self.fp.read() self.bytes = self.bytes + len(data) hashes = int(self.bytes) / self.blocksize while hashes > self.hashes: self.outfp.write('#') self.outfp.flush() self.hashes = self.hashes + 1 return data def close(self): self.outfp.write('\n') # Ask permission to download a file. def askabout(filetype, filename, pwd): prompt = 'Send %s %s to %s ? [ny] ' % (filetype, filename, pwd) while 1: reply = string.lower(string.strip(raw_input(prompt))) if reply in ['y', 'ye', 'yes']: return 1 if reply in ['', 'n', 'no', 'nop', 'nope']: return 0 print 'Please answer yes or no.' # Make a remote directory if it doesn't exist def mkremdir(f, pathname): pwd = f.pwd() try: f.cwd(pathname) except: dirname = os.path.dirname(pathname) if dirname: mkremdir(f, dirname) f.mkd(pathname) f.cwd(pwd) return # Write a dictionary to a file in a way that can be read back using # eval() but is still somewhat readable (i.e. not a single long line). def writedict(dict, f, filename): tempfile = '/tmp/mirrorto.%d.%d' % (os.getpid(), time.time()) fp = open(tempfile, 'w') fp.write('{\n') for key, value in dict.items(): fp.write('%s: %s,\n' % (`key`, `value`)) fp.write('}\n') fp.close() fp = open(tempfile, 'r') f.storlines('STOR '+filename, fp) fp.close() os.unlink(tempfile) main()