#! /usr/bin/env python # -*- coding: ISO-8859-1 -*- # Daniel Pecos Martínez (contact@netpecos.org) # November, 2001 # http://ftpsync.netpecos.org # Use: ftpsync ftpserver [-l localdir] [-r remotedir] [-u user] [-d] [-p password] # [-x exclude_dirs] [-xt exclude_match] [-a ascii_files] # By default, 'localdir'=. 'remotedir'=. , 'user' and 'password' will be asked import os, sys, ftplib, re from os import stat,system from getpass import getpass from stat import ST_MODE, ST_SIZE from string import * def chmod_ascii(asc): mod=[0,0,0] for i in range(3): for j in range(3): if asc[j+i*3]=='r': mod[i]=mod[i]+4 elif asc[j+i*3]=='w': mod[i]=mod[i]+2 elif asc[j+i*3]=='x': mod[i]=mod[i]+1 return '%i%i%i'%(mod[0],mod[1],mod[2]) def modes(file): mod=[0,0,0] m=os.stat(file)[ST_MODE] & 0x0FFF if m & 0x0100: mod[0]=mod[0]+4 if m & 0x0080: mod[0]=mod[0]+2 if m & 0x0040: mod[0]=mod[0]+1 if m & 0x0020: mod[1]=mod[1]+4 if m & 0x0010: mod[1]=mod[1]+2 if m & 0x0008: mod[1]=mod[1]+1 if m & 0x0004: mod[2]=mod[2]+4 if m & 0x0002: mod[2]=mod[2]+2 if m & 0x0001: mod[2]=mod[2]+1 return '%i%i%i'%(mod[0],mod[1],mod[2]) def upload(file,mod): if file[-4:] in ['html','php3','php4','java'] or \ file[-3:] in ['txt','htm','php','cgi','css','log','ini','sql','asc'] or \ file[-3:] in ['c++','cpp','pas','awk','bas','asm','tex','cs'] or \ file[-2:] in ['sh','py','cc','po'] or \ file[-1:] in ['c','h','p'] or \ file in ['./.htaccess','./.htpasswd'] or \ find(file,'.')<0 or \ file in ascii or os.path.basename(file) in ascii: how='ascii' # ascii lfile=open(file,'r') connection.storlines('STOR '+os.path.basename(file), lfile) else: how='binary' # binary lfile=open(file,'rb') connection.storbinary('STOR '+os.path.basename(file),lfile,1024) connection.sendcmd('SITE CHMOD '+mod+' '+os.path.basename(file)) lfile.close() return how def ftp_remove(path,file,type,spc): if (type!='d'): print ' '*spc+'D '+file connection.delete(file) else: print ' '*spc+'--> D '+file try: connection.rmd(file) except: pwd=connection.pwd() connection.cwd(file) try: remotefiles={} tmp=[] callback =lambda line, a=tmp: a.append(line) connection.retrlines('LIST -A',callback) for ls in tmp: if find(ls,'total') < 0: # size:perm:type remotefiles[split(ls)[8]] = (split(ls)[4],chmod_ascii(split(ls)[0][1:]),split(ls)[0][0]) except: remotefiles={} for f in remotefiles.keys(): ftp_remove(os.path.join(path,file),f,remotefiles[f][2],spc+4) connection.cwd(pwd) connection.rmd(file) def help(): print 'ftpsync -- http://www.netpecos.org/projects' print 'Author: Daniel Pecos (contact@netpecos.org), 2001' print print 'Use: ftpsync server [options]' print ' -l dir: local dir to mirror' print ' -r dir: remote dir in server' print ' -u user: ftp user' print ' -p pass: ftp password' print ' -d: delete remote files that not exist in local' print ' -x dir: local dir''s to skip' print ' -xt str: skip files containing "str" on its name' print ' -a files: "files" will be upload in ascii mode' print ' -h: this help' print # -------------------------------------------------------------- def compare(path,ftp_path,spc): connection.cwd(ftp_path) try: remotefiles={} tmp=[] callback =lambda line, a=tmp: a.append(line) connection.retrlines('LIST -A',callback) for ls in tmp: if find(ls,'total') < 0: # size:perm:type remotefiles[split(ls)[8]] = (split(ls)[4],chmod_ascii(split(ls)[0][1:]),split(ls)[0][0]) except: remotefiles={} localfiles=os.listdir(path) for file in localfiles: mode=modes(os.path.join(path,file)) # check re matches=0 for reg in regexp: if re.search(reg,file) or re.search(reg,os.path.basename(file)): matches=1 if os.path.isdir(os.path.join(path,file)): if os.path.join(path,file) in exclude or file in exclude or matches: print ' '*spc+'Skipping '+ file else: if file not in remotefiles.keys(): print ' '*spc+'--> * Creating directory '+ file connection.mkd(file) connection.sendcmd('SITE CHMOD '+mode+' '+os.path.basename(file)) else: if remotefiles[file][1]!=mode: print ' '*spc+'--> m '+file connection.sendcmd('SITE CHMOD '+mode+' '+os.path.basename(file)) else: print ' '*spc+'--> '+file # recurse compare(os.path.join(path,file),file,spc+4) else: if os.path.join(path,file) in exclude or file in exclude or matches: print ' '*spc+'Skipping '+ file else: if file in remotefiles.keys(): size=os.stat(os.path.join(path,file))[ST_SIZE] if int(remotefiles[file][0]) != size: connection.delete(file) print ' '*spc+'u '+file + ' ('+upload(os.path.join(path,file),mode)+')' elif remotefiles[file][1]!=mode: print ' '*spc+'m "'+file + '"' connection.sendcmd('SITE CHMOD '+mode+' '+os.path.basename(file)) else: print ' '*spc+'- '+file else: print ' '*spc+'* '+file + ' ('+upload(os.path.join(path,file),mode)+')' # delete remote files/directories if ftp_del!=0: for rfile in remotefiles.keys(): if rfile not in localfiles: ftp_remove(path,rfile,remotefiles[rfile][2],spc) connection.cwd('..') if len(sys.argv)<2: help() sys.exit() ftp_server=sys.argv[1] ftp_dir='.' localdir='.' ftp_user='' ftp_passwd='' ftp_del=0 exclude=[] regexp=[] ascii=[] i=1; while i < len(sys.argv): if sys.argv[i]=='-l': localdir=sys.argv[i+1] elif sys.argv[i]=='-r': ftp_dir=sys.argv[i+1] elif sys.argv[i]=='-u': ftp_user=sys.argv[i+1] elif sys.argv[i]=='-p': ftp_passwd=sys.argv[i+1] elif sys.argv[i]=='-d': ftp_del=1 elif sys.argv[i]=='-x': i=i+1 while i < len(sys.argv) and sys.argv[i][0]!='-': exclude.append(sys.argv[i]) i=i+1 i=i-1 elif sys.argv[i]=='-xt': i=i+1 while i < len(sys.argv) and sys.argv[i][0]!='-': regexp.append(sys.argv[i]) i=i+1 i=i-1 elif sys.argv[i]=='-a': i=i+1 while i < len(sys.argv) and sys.argv[i][0]!='-': ascii.append(sys.argv[i]) i=i+1 i=i-1 elif sys.argv[i]=='-h': help() sys.exit() i=i+1 if ftp_user=='': ftp_user=raw_input('FTP User: ') if ftp_passwd=='': ftp_passwd=getpass('Type a password for ftp user %s: '%ftp_user) if (ftp_server): try: connection=ftplib.FTP(ftp_server) connection.login(ftp_user,ftp_passwd) #connection.sendcmd('PASV') except: print "Connection error!" sys.exit() try: print 'Synchronizing ...' print compare(localdir,ftp_dir,2) connection.close() print print 'Done.' except: print 'Error! (Please send a bug to bugs@netpecos.org)' else: help()