------------------------------------------------------------------------------
MC logo
FTP Download Client
[^] Code Examples
------------------------------------------------------------------------------
<<Calculator fdld.py Canvas With Events>>
#!/usr/bin/python

import sys
import re
from Tkinter import *
from tkMessageBox import *
from ftplib import *

# Python DESPERATELY needs ++.  This little class could be exended in
# several ways, but it does what I need here.
class counter:
    '''This class takes an initial integer, and delivers a sequence of 
       increasing values.'''
    def __init__(self, i):
        self.val = i

    # This is i++
    def up(self):
        'Increment and get old value'
        i = self.val
        self.val = self.val + 1
        return i

# Close the connection and terminate pgm.
def term(conn):
    "Close the connection, if possible, then quit."
    if conn:
        try:
            conn.quit()
        except:
            try:
                conn.close()
            except:
                "Nothing"
    sys.exit()

# Login window.
class LoginWindow:
    '''The login window asks for the remote host and the user credentials,
       and a button to initiate the login when the fields are ready.'''

    # Generate s label/entry pair for the login window.  These will be 
    # appropriately gridded on row row inside par.  Text box has width
    # width and places its contents into the reference $ref.  If $ispwd,
    # treat it as a password entry box.  Returns the text variable which
    # gives access to the entry.
    def genpair(self, row, text, width, ispwd=False):
        "Generate a label and entry box pair."
        tbut = Label(self.main, text = text)
        tvar = StringVar(self.main)
        lab = Entry(self.main, background='white', 
                    foreground='black', 
                    textvariable=tvar,
                    width=width)
        if ispwd: lab.configure(show='*')
        tbut.grid(row=row, column=0, sticky='nse')
        lab.grid(row=row, column=1, sticky='nsw')

        return tvar

    # Log into the remote host.  If successful, start the directory loader.
    # Modes are: 1: Anonymous, 2: User, 3: Return, which does anon if the
    # user infor was not filled in, and user otw.
    def do_login(self, mode):
        '''Login action'''
        
        host = self.host.get()
        acct = self.acct.get()
        password = self.password.get()

        # Adjust user data by mode.
        if mode == 1 or (mode == 3 and not acct and not password):
            acct ='anonymous'
            if not password:
                password = 'anonymous'

        # Make sure we're all filled in.
        if not host or not acct or not password:
            showerror(self.main,"You must provide a user name and password.")
            return

        # Attempt to connect to the remote host and log in
        try:
            self.conn = FTP(host, acct, password)
            self.conn.set_pasv(True)
        except:
            descr = sys.exc_info()[1]
            showerror(self, descr)
            return

        self.listwin.setconn(self.conn)
        self.main.destroy()

    def __init__(self, main, listwin, titfont, titcolor):
        self.main = Toplevel(main)
        self.main.title('FTP Login')

        self.listwin = listwin
        self.conn = 0

        # This counts through the rows, which makes it easier to modify
        # the program.
        row = counter(0)

        # Label at the top of window.
        toplab = Label(self.main, text="FTP Server Login", justify='center',
                       font=titfont, foreground=titcolor)
        toplab.grid(row=row.up(), column=0, columnspan=2, sticky='news')

        # Hostname entry
        self.host = self.genpair(row.up(), 'Host:', 25)

        # Login buttons
        bframe = Frame(self.main)
        bframe.grid(row=row.up(), column=0, columnspan=2, sticky='news')
        go = Button(bframe, text='Anon. Login',
                    command=lambda mode=1: self.do_login(mode))
        go.pack(side='left', expand='yes', fill='both')
        go = Button(bframe, text='User Login',
                    command=lambda mode=2: self.do_login(mode))
        go.pack(side='left', expand='yes', fill='both')

        # Login and password entries.
        self.acct = self.genpair(row.up(), 'Login:', 15)
        self.password = self.genpair(row.up(), 'Password:', 15, True)

        stop = Button(self.main, text='Exit', 
                      command=lambda c=self.conn: term(c))
        stop.grid(row=row.up(),column=0, columnspan=2, 
                  sticky='news')

        # CR same as pushing login.
        self.main.bind('<KeyPress-Return>',
                       lambda event, mode=3: self.do_login(mode))

class FileWindow(Frame):
    def __init__(self, main):
        Frame.__init__(self, main)

        # Set up the title appearance.
        titfont = ( 'Arial', 16, 'bold' )
        titcolor = '#228800'

        self.conn = 0

        # Label at top.
        toplab = Label(self, text='FTP Download Agent',
                       justify='center', font=titfont,
                       foreground=titcolor)
        toplab.pack(side=TOP, fill=X)

        # Status label.
        self.statuslab = Label(self, text='Not Logged In',
                          justify = 'center')
        self.statuslab.pack(side=TOP, fill=X)

        # Exit button
        exbut = Button(self, text = 'Exit', 
                       command=lambda c=self.conn: term(c))
        exbut.pack(side=BOTTOM, fill=X)

        # List area with scroll bar.  The list area is disabled since we
        # don't want the user to type into it.
        self.listarea = Text(self, height=10, width=40, cursor='sb_left_arrow',
                             state=DISABLED)
        scr = Scrollbar(self, command=self.listarea.yview)
        self.listarea.configure(yscrollcommand=scr.set)
        scr.pack(side=RIGHT, fill=Y)
        self.listarea.pack(side=LEFT)

        # This RE is needed to match file names from DIR
        self.filere = re.compile('^[\-d]([r\-][w\-][x\-]){3}')

        # Bind the system exit button to our exit.
        main.protocol('WM_DELETE_WINDOW', lambda c=self.conn: term(c))

        # Create the login window.
        LoginWindow(main, self, titfont, titcolor)

    # This is the call-back for the FTP libraries which receives lines of
    # of dir listing from the FTP server.
    def dirent(self, line):
        "Handle one line of the directory listing."

        # Real lines start with the perm bits.  And we don't want specials.
        if self.filere.search(line) == None: return

        # Extract the useful parts, toss the bones.  The limit keeps us from
        # dividing file names containing spaces.
        parts = line.split(None, 9)
        if len(parts) < 9: return
        fn = parts.pop()
        if fn == '..': self.sawdots = True
        if parts[0][0] == 'd':
            self.dirs.append(fn)
        else:
            self.files.append(fn)

    # Change the color of a tag for entering and leaving.  Unfortunately, there
    # is no active color for tags in a text box.
    def recolor(self, tag, color):
        "Set the color of a particular tagged area in the list area"
        self.listarea.tag_config(tag, foreground=color)

    # Do a CD and load the contents.  If there is no directory name, skip
    # the CD.
    def load_dir(self, dir):
        "Change to a directory and load its content into the listing window."
        if dir:
            try:
                self.conn.cwd(dir)
            except:
                showerror(self, sys.exc_info()[1])
            self.statuslab.configure(text="[Loading " + dir + "]")
        else:
            self.statuslab.configure(text='[Loading Home Dir]')
        self.update()

        # Get the list of files.
        self.files = [ ]
        self.dirs = [ ]
        self.sawdots = False
        self.conn.retrlines('LIST', self.dirent)

        # Add .. if not present, then sort the list.
        if not self.sawdots: self.dirs.append('..')
        self.files.sort()
        self.dirs.sort()

        # Clear the old contents from the directory listing box.
        self.listarea.configure(state='normal')
        self.listarea.delete('1.0',END)

        # Fill in the directories.  Bind for directory load (us).
        ct = 0
        while self.dirs != []:
            fn = self.dirs.pop(0)
            tagname = "fn" + str(ct)
            self.listarea.insert(END, fn+"\n", tagname)
            self.listarea.tag_config(tagname, foreground='#4444ff')
            self.listarea.tag_bind(tagname, '<Button-1>', 
                                  lambda w, f=fn: self.load_dir(f))
            self.listarea.tag_bind(tagname, '<Enter>', 
                                  lambda w, t=tagname, c='#0000aa':
                                   self.recolor(t,c), '+')
            self.listarea.tag_bind(tagname, '<Leave>', 
                                  lambda w, t=tagname, c='#4444ff':
                                   self.recolor(t,c), '+')
            ct = ct + 1

        # Fill in the files. Bind for download.
        while self.files != []:
            fn = self.files.pop(0)
            tagname = "fn" + str(ct)
            self.listarea.insert(END, fn+"\n", tagname)
            self.listarea.tag_config(tagname, foreground='red')
            self.listarea.tag_bind(tagname, '<Button-1>', 
                                  lambda w, f=fn: self.dld_file(f))
            self.listarea.tag_bind(tagname, '<Enter>', 
                                  lambda w, t=tagname, c='#880000':
                                   self.recolor(t,c), '+')
            self.listarea.tag_bind(tagname, '<Leave>', 
                                  lambda w, t=tagname, c='red':
                                   self.recolor(t,c), '+')
            ct = ct + 1

        # Lock it up so the user can't mess with it.
        self.listarea.configure(state='disabled')

        # Update the status label.
        try:
            loc = self.conn.pwd()
        except:
            showerror(self, sys.exc_info()[1])
            loc = '???'
        self.statuslab.configure(text=loc)

    # Download the file.
    def dld_file(self,fn):
        "Perform a file download."

        # Attempt to open the local file.
        try:
            ofn = open(fn, 'w')
        except:
            showerror(self, sys.exc_info()[1])
            return

        # Announce.
        self.statuslab.configure(text="[Retrieving" + fn + "]")
        self.update()

        # Get the file.
        try:
            self.conn.retrbinary('RETR ' + fn, ofn.write)
        except:
            showerror(self, sys.exc_info()[1])
            self.statuslab.configure(text='')
        else:
            self.statuslab.configure(text='Got ' + fn)
        ofn.close()

    # This is a hook that the login window calls after a successful login.
    # The login window makes the connection and attempts to login.  When this
    # succeeds, it calls setconn() and destroys itself.  Setconn records the
    # connection (which the login box created), then does the initial
    # directory load.
    def setconn(self, conn):
        "Set the connection and perform the initial directory load"

        self.conn = conn
        self.load_dir('')

# Create the main window, set the default colors, create the GUI, then
# fire the sucker up.
main = Tk()
main.title("FTP Download")
main.option_add("*background", '#E6E6FA')
main.option_add("*activebackground", '#FFE6FA')
main.option_add("*foreground", '#0000FF')
main.option_add("*activeforeground", '#0000FF')
FileWindow(main).pack()

main.mainloop()

<<Calculator Canvas With Events>>