Below is the file 'tftp-console.py' from this revision. You can also download the file.

#!/usr/bin/env python

# pygtk stuff
import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade
import gobject
import os
import sys
import socket

import gtktftp

class TFTPDialog:
	def __init__(self):
		port = 69
		try:
			self.tftp = gtktftp.gTFTPServer(port)
		except socket.error:
			self.display_error("Unable to bind to the TFTP port (%d/UDP)" % port, """\
Some platforms require elevated privileges to bind to low ports. Consult
your platform documentation and the README file that came with this
software. For your reference, you are on the '%s' platform.""" % sys.platform)
			raise
		except:
			self.display_error("Unable to initialise TFTP server", """\
This is almost certainly a bug. Please contact the author:
	Grahame Bowland <grahame@angrygoats.net>
Include as much information as possible. Please also let
him know you are on the '%s' platform.""" % sys.platform)
			raise
		self.tftp_signals = []
		self.create_tftp_signals()
		# load the glade file; grab widget references
		xml = open("tftp-console.glade").read()
		self.glade = gtk.glade.xml_new_from_buffer(xml, len(xml))
		self.window = self.glade.get_widget("main_window")
		self.window.connect("destroy", self.window_destroy)
		self.execute_button = self.glade.get_widget("execute_button")
		self.execute_button.connect("clicked", self.execute_button_clicked)
		self.add_button = self.glade.get_widget("add_button")
		self.add_button.connect("clicked", self.add_button_clicked)
		self.remove_button = self.glade.get_widget("remove_button")
		self.remove_button.connect("clicked", self.remove_button_clicked)
		self.status_bar = self.glade.get_widget("status_bar")
		self.tree_view = self.glade.get_widget("file_tree_view")
		quit_menu = self.glade.get_widget("quit1")
		about_menu = self.glade.get_widget("about1")
		# set up the list store
		self.list_store = gtk.ListStore(str, str)
		self.tree_view.set_model(self.list_store)
		# filename column
		self.filename_column = gtk.TreeViewColumn("Filename")
		self.tree_view.append_column(self.filename_column)
		self.filename_cell = gtk.CellRendererText()
		self.filename_column.pack_start(self.filename_cell, True)
		self.filename_column.set_attributes(self.filename_cell, text=0)
		# size column
		self.size_column = gtk.TreeViewColumn("Size")
		self.tree_view.append_column(self.size_column)
		self.size_cell = gtk.CellRendererText()
		self.size_column.pack_start(self.size_cell, True)
		self.size_column.set_attributes(self.size_cell, text=1)
		# callbacks
		treeselection = self.tree_view.get_selection()
		treeselection.connect("changed", self.tree_view_update)
		quit_menu.connect("activate", self.window_destroy)
		about_menu.connect("activate", self.about)
		# initialise list buttons
		self.remove_button.set_sensitive(False)
		self.window.show_all()
	def create_tftp_signals(self):
		# hook up the TFTP server to our UI events
		to_connect = [("data-received", self.tftp_data_received),
			      ("data-sent", self.tftp_data_sent),
			      ("file-received", self.tftp_file_received),
			      ("file-sent", self.tftp_file_sent),
			      ("error-received", self.tftp_error_received),
			      ("error-sent", self.tftp_error_sent)]
		self.tftp_signals += map(lambda x: self.tftp.connect(x[0], x[1]), to_connect)
	def disconnect_tftp_signals(self):
		map(self.tftp.disconnect, self.tftp_signals)
	def _add_file(self, filename):
		data = open(filename, "rb").read()
		length = len(data)
		if length < 1024: length = str(length) + " bytes"
		elif length < 1048576: length = str(length / 1024) + " kilobytes"
		else: length = str(length / 1048576) + " megabytes"
		basename = os.path.basename(filename)
		if self.tftp.tftp.store.has_export(basename):
			dialog = gtk.Dialog(title="Replace file?", parent=None,
						flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
						buttons=(gtk.STOCK_NO,gtk.RESPONSE_NO,
							 gtk.STOCK_YES, gtk.RESPONSE_YES))
			label = gtk.Label('<span size="large" weight="bold">There is already a file named "%s" in the exported files list.</span>\n\nDo you wish to replace it?' % (basename))
			label.set_use_markup(True)
			dialog.set_has_separator(False)
			dialog.set_border_width(12)
			dialog.vbox.pack_start(label, True, True, 0)
			label.show()
			response = dialog.run()
			dialog.destroy()
			if response != gtk.RESPONSE_YES: return False
		else:
			iter = self.list_store.append([basename, length])
		self.tftp.tftp.store.set_export(basename, data)
		return True
	def display_error(self, description, explanation):
		dialog = gtk.Dialog(title="Error", parent=None,
					flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
					buttons=(gtk.STOCK_OK,gtk.RESPONSE_OK))
		label = gtk.Label('<span size="large" weight="bold">%s</span>\n\n%s' % (description, explanation))
		label.set_use_markup(True)
		dialog.set_has_separator(False)
		dialog.set_border_width(12)
		dialog.vbox.pack_start(label, True, True, 0)
		label.show()
		response = dialog.run()
		dialog.destroy()
	def update_bar(self, str):
		self.status_bar.pop(1)
		self.status_bar.push(1, str)
	def tftp_data_received(self, widget, address, filename, bytes):
		self.update_bar("%s: Receiving %s (%d bytes)" % (address, filename, bytes))
	def tftp_data_sent(self, widget, address, filename, bytes):
		self.update_bar("%s: Sending %s (%d bytes)" % (address, filename, bytes))
	def tftp_file_received(self, widget, address, filename):
		self.update_bar("%s: Sucessfully received %s" % (address, filename))
		filechooser = gtk.FileChooserDialog(title="Save received file", \
					action=gtk.FILE_CHOOSER_ACTION_SAVE,
					buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
						 gtk.STOCK_OK, gtk.RESPONSE_OK))
		filechooser.set_current_name(filename)
		response = filechooser.run()
		if response == gtk.RESPONSE_OK:
			save_to = filechooser.get_filename()
			open(save_to, "w").write(self.tftp.tftp.store.get_received(filename))
		filechooser.destroy()
		self.tftp.tftp.store.clear_received(filename)
	def tftp_file_sent(self, widget, address, filename):
		self.update_bar("%s: Sucessfully sent %s" % (address, filename))
	def tftp_error_received(self, widget, address, code, mesg):
		self.update_bar("%s: Error received: %s (%s)" % (address, mesg, code))
	def tftp_error_sent(self, widget, address, code, mesg):
		self.update_bar("%s: Error sent: %s (%s)" % (address, mesg, code))
	def new_tftp_connection(self, widget, user=None):
		pass
	def window_destroy(self, widget, user=None):
		gtk.main_quit()
	def about(self, widget, user=None):
		about = gtk.AboutDialog()
		about.set_name("TFTP Console")
		about.set_version("0.1")
		about.set_copyright("Copyright 2005 Grahame Bowland")
		about.set_website("http://grahame.angrygoats.net/")
		about.set_authors(["Grahame Bowland"])
		about.set_license("""\
TFTP Console
Copyright (C) 2005 Grahame Bowland

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
""")
		about.run()
		about.destroy()
	def add_button_clicked(self, widget, user=None):
		filechooser = gtk.FileChooserDialog(title="Select files to export", \
					action=gtk.FILE_CHOOSER_ACTION_OPEN,
					buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
						 gtk.STOCK_OK, gtk.RESPONSE_OK))
		filechooser.set_select_multiple(True)
		response = filechooser.run()
		if response == gtk.RESPONSE_OK:
			filenames = filechooser.get_filenames()
			map(self._add_file, filenames)
		filechooser.destroy()
	def remove_button_clicked(self, widget, user=None):
		treeselection = self.tree_view.get_selection()
		(model, iter) = treeselection.get_selected()
		if iter:
			filename = self.list_store.get_value(iter, 0)
			self.list_store.remove(iter)
			self.tftp.tftp.store.clear_export(filename)
			self.tree_view_update()
	def tree_view_update(self, iter=None):
		treeselection = self.tree_view.get_selection()
		(model, iter) = treeselection.get_selected()
		if iter: self.remove_button.set_sensitive(True)
		else: self.remove_button.set_sensitive(False)
	def execute_button_clicked(self, widget, user=None):
		# do SNMP stuff, then TFTP stuff, then...
		widget.set_sensitive(False)

if __name__ == '__main__':
	tftpdialog = TFTPDialog()
	gtk.main()