本來是別人寫的一個 ftp download python script,因為不知道到底是不是有在下載中(?),所以我多加了一個顯示現在 download 幾 percent 的 function:

def ftp_get(src_dir, name, dst_dir):
    def ftp_callback(chunk, file_size, dst_file):
        if not hasattr(ftp_callback, "progress_size"):
            ftp_callback.progress_size = 0
        if not hasattr(ftp_callback, "progress_percent"):
            ftp_callback.progress_percent = 0
        dst_file.write(chunk)
        ftp_callback.progress_size += len(chunk)
        tmp = ftp_callback.progress_size * 100 / file_size
        if ftp_callback.progress_percent != tmp:
            ftp_callback.progress_percent = tmp
            sys.stdout.write('\r%s' % str(ftp_callback.progress_percent) + '%')
            sys.stdout.flush()
    ftp = ftplib.FTP(ftp_url)
    ftp.login(ftp_account, ftp_password)
    ftp_dir = ftp_root + src_dir
    ftp.cwd(ftp_dir)
    ftp_file = ftp.nlst(name)
    if len(ftp_file) != 1:
        print "Confused with ftp files, please check server site"
        exit(-1)
    dst_path = "%s/%s" % (dst_dir, ftp_file[0])
    file_size = ftp.size(ftp_dir + '/' + ftp_file[0])
    dst_file = open(dst_path, "wb")
    try:
        print "Downloading " + ftp_file[0]
        ftp.retrbinary('RETR %s/%s' % (ftp_dir, ftp_file[0]), lambda chunk: ftp_callback(chunk, file_size, dst_file))
    except Exception, e:
        print "ERROR: %s" % e
        os.unlink(dst_path)
    else:
        print ''
        print "Download %s success" % ftp_file[0]

關鍵點在於 ftp.retrbinary('RETR %s/%s' % (ftp_dir, ftp_file[0]), lambda chunk: ftp_callback(chunk, file_size, dst_file)) 的最後一個參數,是一個 callback function,接受一個參數為這次 download 的 chunk,chunk 是一個檔案的其中一小塊部分,每次在 download 完一個 chunk 後就會自動呼叫這個 function,所以可以先用 file_size = ftp.size(ftp_dir + '/' + ftp_file[0]) 拿到檔案的總大小,然後在 callback 中用 len(chunk) 算這次拿到的 chunk 大小,做累加就可以算出現在 download 到幾 percent 了。累計的部分用 function 的 attribute 來做累加,算是一個小 tricky 的地方。percent 輸出的部分則用 sys.stdout.write,這樣才能用 \r 回到一行的最開始,並把這一行洗掉。

Ref: