home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C / Applications / Python 1.3 / source code / Tools / scripts / ftpmirror.py < prev    next >
Encoding:
Python Source  |  1995-12-17  |  7.7 KB  |  300 lines  |  [TEXT/R*ch]

  1. #! /usr/local/bin/python
  2.  
  3. # Mirror a remote ftp subtree into a local directory tree.
  4. # Basic usage: ftpmirror [options] host remotedir localdir
  5. #
  6. # XXX To do:
  7. # - handle symbolic links
  8. # - back up .mirrorinfo before overwriting
  9. # - use pickles for .mirrorinfo?
  10.  
  11. import os
  12. import sys
  13. import time
  14. import getopt
  15. import string
  16. import ftplib
  17. from fnmatch import fnmatch
  18.  
  19. usage_msg = """
  20. usage: ftpmirror [-v] [-q] [-i] [-m] [-n] [-r] [-s pat]
  21.                  [-l username [-p passwd [-a account]]]
  22.          hostname [remotedir [localdir]]
  23. -v: verbose
  24. -q: quiet
  25. -i: interactive mode
  26. -m: macintosh server (NCSA telnet 2.4) (implies -n -s '*.o')
  27. -n: don't log in
  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
  31. hostname: remote host
  32. remotedir: remote directory (default initial)
  33. localdir: local directory (default current)
  34. """
  35. def usage(*args):
  36.     sys.stdout = sys.stderr
  37.     for msg in args: print msg
  38.     print usage_msg
  39.     sys.exit(2)
  40.  
  41. verbose = 1 # 0 for -q, 2 for -v
  42. interactive = 0
  43. mac = 0
  44. rmok = 0
  45. nologin = 0
  46. skippats = []
  47.  
  48. def main():
  49.     global verbose, interactive, mac, rmok, nologin
  50.     try:
  51.         opts, args = getopt.getopt(sys.argv[1:], 'a:bil:mnp:qrs:v')
  52.     except getopt.error, msg:
  53.         usage(msg)
  54.     login = ''
  55.     passwd = ''
  56.     account = ''
  57.     for o, a in opts:
  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')
  69.     host = args[0]
  70.     remotedir = ''
  71.     localdir = ''
  72.     if args[1:]:
  73.         remotedir = args[1]
  74.         if args[2:]:
  75.             localdir = args[2]
  76.             if args[3:]: usage('too many arguments')
  77.     #
  78.     f = ftplib.FTP()
  79.     if verbose: print 'Connecting to %s...' % host
  80.     f.connect(host)
  81.     if not nologin:
  82.         if verbose:
  83.             print 'Logging in as %s...' % (login or 'anonymous')
  84.         f.login(login, passwd, account)
  85.     if verbose: print 'OK.'
  86.     pwd = f.pwd()
  87.     if verbose > 1: print 'PWD =', `pwd`
  88.     if remotedir:
  89.         if verbose > 1: print 'cwd(%s)' % `remotedir`
  90.         f.cwd(remotedir)
  91.         if verbose > 1: print 'OK.'
  92.         pwd = f.pwd()
  93.         if verbose > 1: print 'PWD =', `pwd`
  94.     #
  95.     mirrorsubdir(f, localdir)
  96.  
  97. def mirrorsubdir(f, localdir):
  98.     pwd = f.pwd()
  99.     if localdir and not os.path.isdir(localdir):
  100.         if verbose: print 'Creating local directory', localdir
  101.         makedir(localdir)
  102.     infofilename = os.path.join(localdir, '.mirrorinfo')
  103.     try:
  104.         text = open(infofilename, 'r').read()
  105.     except IOError, msg:
  106.         text = '{}'
  107.     try:
  108.         info = eval(text)
  109.     except (SyntaxError, NameError):
  110.         print 'Bad mirror info in %s' % infofilename
  111.         info = {}
  112.     subdirs = []
  113.     listing = []
  114.     if verbose: print 'Listing remote directory %s...' % pwd
  115.     f.retrlines('LIST', listing.append)
  116.     for line in listing:
  117.         if verbose > 1: print '-->', `line`
  118.         if mac:
  119.             # Mac listing has just filenames;
  120.             # trailing / means subdirectory
  121.             filename = string.strip(line)
  122.             mode = '-'
  123.             if filename[-1:] == '/':
  124.                 filename = filename[:-1]
  125.                 mode = 'd'
  126.             infostuff = ''
  127.         else:
  128.             # Parse, assuming a UNIX listing
  129.             words = string.split(line)
  130.             if len(words) < 6:
  131.                 if verbose > 1: print 'Skipping short line'
  132.                 continue
  133.             if words[-2] == '->':
  134.                 if verbose > 1:
  135.                     print 'Skipping symbolic link %s -> %s' % \
  136.                           (words[-3], words[-1])
  137.                 continue
  138.             filename = words[-1]
  139.             if filename in ('.', '..'):
  140.                 if verbose > 1: print 'Skipping . or ..'
  141.                 continue
  142.             infostuff = words[-5:-1]
  143.             mode = words[0]
  144.         skip = 0
  145.         for pat in skippats:
  146.             if fnmatch(filename, pat):
  147.                 if verbose > 1:
  148.                     print 'Skip pattern', pat,
  149.                     print 'matches', filename
  150.                 skip = 1
  151.                 break
  152.         if skip:
  153.             continue
  154.         if mode[0] == 'd':
  155.             if verbose > 1:
  156.                 print 'Remembering subdirectory', filename
  157.             subdirs.append(filename)
  158.             continue
  159.         if info.has_key(filename) and info[filename] == infostuff:
  160.             if verbose > 1:
  161.                 print 'Already have this version of', filename
  162.             continue
  163.         fullname = os.path.join(localdir, filename)
  164.         if interactive:
  165.             doit = askabout('file', filename, pwd)
  166.             if not doit:
  167.                 if not info.has_key(filename):
  168.                     info[filename] = 'Not retrieved'
  169.                 continue
  170.         try:
  171.             fp = open(fullname, 'w')
  172.         except IOError, msg:
  173.             print "Can't create %s: %s" % (fullname, str(msg))
  174.             continue
  175.         if verbose:
  176.             print 'Retrieving %s from %s as %s...' % \
  177.                   (filename, pwd, fullname)
  178.         if verbose:
  179.             fp1 = LoggingFile(fp, 1024, sys.stdout)
  180.         else:
  181.             fp1 = fp
  182.         t0 = time.time()
  183.         f.retrbinary('RETR ' + filename, fp1.write, 8*1024)
  184.         t1 = time.time()
  185.         bytes = fp.tell()
  186.         fp.close()
  187.         if fp1 != fp:
  188.             fp1.close()
  189.         info[filename] = infostuff
  190.         writedict(info, infofilename)
  191.         if verbose:
  192.             dt = t1 - t0
  193.             kbytes = bytes / 1024.0
  194.             print int(round(kbytes)),
  195.             print 'Kbytes in',
  196.             print int(round(dt)),
  197.             print 'seconds',
  198.             if t1 > t0:
  199.                 print '(~%d Kbytes/sec)' % \
  200.                       int(round(kbytes/dt),)
  201.             print
  202.     #
  203.     # Remove local files that are no longer in the remote directory
  204.     if not localdir: names = os.listdir(os.curdir)
  205.     else: names = os.listdir(localdir)
  206.     for name in names:
  207.         if name[0] == '.' or info.has_key(name) or name in subdirs:
  208.             continue
  209.         fullname = os.path.join(localdir, name)
  210.         if not rmok:
  211.             if verbose:
  212.                 print 'Local file', fullname,
  213.                 print 'is no longer pertinent'
  214.             continue
  215.         if verbose: print 'Removing local file', fullname
  216.         try:
  217.             os.unlink(fullname)
  218.         except os.error, msg:
  219.             print "Can't remove local file %s: %s" % \
  220.                   (fullname, str(msg))
  221.     #
  222.     # Recursively mirror subdirectories
  223.     for subdir in subdirs:
  224.         if interactive:
  225.             doit = askabout('subdirectory', subdir, pwd)
  226.             if not doit: continue
  227.         if verbose: print 'Processing subdirectory', subdir
  228.         localsubdir = os.path.join(localdir, subdir)
  229.         pwd = f.pwd()
  230.         if verbose > 1:
  231.             print 'Remote directory now:', pwd
  232.             print 'Remote cwd', subdir
  233.         try:
  234.             f.cwd(subdir)
  235.         except ftplib.error_perm, msg:
  236.             print "Can't chdir to", subdir, ":", msg
  237.         else:
  238.             if verbose: print 'Mirroring as', localsubdir
  239.             mirrorsubdir(f, localsubdir)
  240.             if verbose > 1: print 'Remote cwd ..'
  241.             f.cwd('..')
  242.         newpwd = f.pwd()
  243.         if newpwd != pwd:
  244.             print 'Ended up in wrong directory after cd + cd ..'
  245.             print 'Giving up now.'
  246.             break
  247.         else:
  248.             if verbose > 1: print 'OK.'
  249.  
  250. # Wrapper around a file for writing to write a hash sign every block.
  251. class LoggingFile:
  252.     def __init__(self, fp, blocksize, outfp):
  253.         self.fp = fp
  254.         self.bytes = 0
  255.         self.hashes = 0
  256.         self.blocksize = blocksize
  257.         self.outfp = outfp
  258.     def write(self, data):
  259.         self.bytes = self.bytes + len(data)
  260.         hashes = int(self.bytes) / self.blocksize
  261.         while hashes > self.hashes:
  262.             self.outfp.write('#')
  263.             self.outfp.flush()
  264.             self.hashes = self.hashes + 1
  265.         self.fp.write(data)
  266.     def close(self):
  267.         self.outfp.write('\n')
  268.  
  269. # Ask permission to download a file.
  270. def askabout(filetype, filename, pwd):
  271.     prompt = 'Retrieve %s %s from %s ? [ny] ' % (filetype, filename, pwd)
  272.     while 1:
  273.         reply = string.lower(string.strip(raw_input(prompt)))
  274.         if reply in ['y', 'ye', 'yes']:
  275.             return 1
  276.         if reply in ['', 'n', 'no', 'nop', 'nope']:
  277.             return 0
  278.         print 'Please answer yes or no.'
  279.  
  280. # Create a directory if it doesn't exist.  Recursively create the
  281. # parent directory as well if needed.
  282. def makedir(pathname):
  283.     if os.path.isdir(pathname):
  284.         return
  285.     dirname = os.path.dirname(pathname)
  286.     if dirname: makedir(dirname)
  287.     os.mkdir(pathname, 0777)
  288.  
  289. # Write a dictionary to a file in a way that can be read back using
  290. # rval() but is still somewhat readable (i.e. not a single long line).
  291. def writedict(dict, filename):
  292.     fp = open(filename, 'w')
  293.     fp.write('{\n')
  294.     for key, value in dict.items():
  295.         fp.write('%s: %s,\n' % (`key`, `value`))
  296.     fp.write('}\n')
  297.     fp.close()
  298.  
  299. main()
  300.