#
# FtpCube
# Copyright (C) 2001 Michael Gilfix
#
# This file is part of FtpCube.
#
# You should have received a file COPYING containing license terms
# along with this program; if not, write to Michael Gilfix
# (mgilfix@eecs.tufts.edu) for a copy.
#
# This version of FtpCube is open source; you can redistribute it and/or
# modify it under the terms listed in the file COPYING.
#
# 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.
#

import gtk, GtkExtra
import string # For backwards compatibility with 1.5.x
import sys, os
import main, app, ftp, threads

from intl import _

# For compatibility with win32 pygtk
if sys.platform == "win32":
	import GTKconst
	GTK = GTKconst
else:
	import GTK

class CtrlWin:

	"""The control window object

	The control window object provides the framework for displaying and creating
	the ftp control objects, such as queuing and threading methods. These objects
	handle user display tasks and high-level manipulation of the ftp backend.
	The control window displays all of the status information concerning actual
	transfers and queuing of transfers. The CtrlWin object provides a consilated
	interface to the lower level sub-objects, each of which are responsible for
	displaying a control "tab" and implementing its related functions.

	There should only be one instance of the control window and it is provided
	a reference to its container from the Application object upon creation."""

	def __init__ (self, container):
		"""Creates the CtrlWin object and displays its contents within the
		container box provided by the Application object. The sub objects
		that occupy the tabs of the notebook are also created."""

		box = gtk.GtkVBox ()
		box.set_usize (275, -1)
		box.show ()
		container.add (box)

		self.notebook = gtk.GtkNotebook ()
		self.notebook.set_tab_pos (gtk.POS_TOP)
		box.pack_start (self.notebook)
		self.notebook.show ()

		status_box = gtk.GtkHBox ()
		status_box.set_usize (-1, 15)
		status_box.show ()
		box.pack_start (status_box, expand=gtk.FALSE)
		self.status_init (status_box)

		self.queue_tab = QueueTab (self)
		label = gtk.GtkLabel (_("Queue"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.queue_tab.get_page (), label)

		self.threads_tab = ThreadsTab (self)
		label = gtk.GtkLabel (_("Threads"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.threads_tab.get_page (), label)

		self.download_tab = DownloadTab (self)
		label = gtk.GtkLabel (_("Downloads"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.download_tab.get_page (), label)

		self.failure_tab = FailureTab (self)
		label = gtk.GtkLabel (_("Failures"))
		label.set_padding (2, 2)
		label.show ()
		self.notebook.append_page (self.failure_tab.get_page (), label)

		# Default to the thread page
		self.notebook.set_page (1)

	def status_init (self, container):
		"""Initializes the status bar within the status 'container', which
		displays information about the queuing process. The label describes
		how many items remain in the queue, and the progress bar indicates
		how many queuing items have been processed as a percentage of the
		batch queueing."""

		table = gtk.GtkTable (2, 1)
		table.show ()
		container.pack_start (table, padding=10)

		packer = gtk.GtkPacker ()
		packer.show ()
		table.attach (packer, 0, 1, 0, 1)
		self.queue_label = gtk.GtkLabel (_("0 Queued"))
		self.queue_label.show ()
		packer.add (self.queue_label, anchor=GTK.ANCHOR_WEST)

		self.queue_progress = gtk.GtkProgressBar ()
		self.queue_progress.set_usize (-1, 7)
		table.attach (self.queue_progress, 1, 2, 0, 1, yoptions=GTK.EXPAND)

		# We default to having no progress out of no items
		self.progress = (0, 0)

	def status_queued_add (self, queued):
		"""Updates the queuing status bar to reflect that one more item
		has been added to the queue and displays the total number of items
		'queued' remaining in the queue."""

		cur, max = self.progress
		max = max + 1
		self.progress = (cur, max)
		self.queue_progress.show ()
		self.queue_progress.update (float (cur) / float (max))
		self.queue_label.set_text (_("%d Queued") %queued)

	def status_queued_remove (self, queued):
		"""Updates the queueing status bar to reflect that an item has
		just been removed and is being processed and displays the total
		number of items 'queued' remaining in the queue."""

		if queued == 0:
			self.status_queue_clear ()
		else:
			cur, max = self.progress
			cur = cur + 1
			self.progress = (cur, max)
			self.queue_progress.update (float (cur) / float (max))
			self.queue_label.set_text (_("%d Queued") %queued)

	def status_queue_clear (self):
		"""Updates to the status bar to reflect that the queue is empty."""

		self.queue_progress.hide ()
		self.progress = (0, 0)
		self.queue_label.set_text (_("0 Queued"))

	def add_to_queue (self, hash):
		"""Provides a high level interface to add a list of files from an
		FTP site into the download queue. Each file entry in the
		hash['files'] key is a tuple of (name, written size, date, flags,
		true size)."""

		for name, size, date, flags, truesize in hash['files']:
			self.queue_tab.add_to_queue ({
				'file'        : name,
				'server'      : hash['server'],
				'port'        : hash['port'],
				'username'    : hash['username'],
				'password'    : hash['password'],
				'remote_path' : hash['remote_path'],
				'local_path'  : hash['local_path'],
				'size'        : truesize,
				'attempt'     : 1,
				'maxattempts' : hash['maxattempts'],
				'direction'   : hash['direction'],
				'flags'       : flags,
				'method'      : hash['method'],
			})

	def add_to_downloads (self, file):
		"""Adds a file to the completed downloads tab."""

		self.download_tab.add_to_list (file)

	def add_to_failure (self, hash):
		"""Adds the structure stored in the dictionary into the failures
		tab."""

		self.failure_tab.add_to_list (hash)

	def attach_main_thread (self, thread):
		"""Places the main thread object 'thread' into the list of threads
		in the ThreadTab object for display."""

		self.threads_tab.attach_main_thread (thread)

	def get_thread_list (self):
		"""Returns a list of active threads from the ThreadTab."""

		return self.threads_tab.get_thread_list ()

class QueueTab:

	"""The Queueing Tab

	Displays a list of the files waiting in the queue and provides several
	buttons for manipulating the processing of the queue, such as a pausing
	button, a clearing button, and a save/load button."""

	headings = [ _('Host'), _('Filename'), _('Size'), _('Attempt'), _('Direction'), _('Port'),
	             _('Username'), _('Password'), _('Remote Path'), _('Local Path'), _('Type'),
	             _('Method') ]

	def __init__ (self, ctrl):
		"""Creates the queueing tab and sets up the appropriate widgets. Also
		initializaes the queue to empty. 'ctrl' is a reference to the parent
		CtrlWin object."""

		# Initialize instance variables
		self.ctrlwin = ctrl
		self.disabled = 0                # For enabling and disabling the queueing process

		self.page = gtk.GtkVBox ()
		self.page.show ()

		box = gtk.GtkHBox ()
		box.show ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)
		toolbar.set_space_size (10)
		toolbar.set_space_style (GTK.TOOLBAR_SPACE_LINE)
		toolbar.show ()
		box.pack_start (toolbar, padding=6)
		self.page.pack_start (box, expand=gtk.FALSE, padding=4)

		enable_pic, enable_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "thread_idle.xpm"))
		clear_pic, clear_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "clear.xpm"))
		load_pic, load_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "load_queue.xpm"))
		save_pic, save_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "save_queue.xpm"))

		toolbar.append_element (GTK.TOOLBAR_CHILD_TOGGLEBUTTON, None,
			"", _("Enable Queuing Process"), _("Enable Queuing Process"),
			gtk.GtkPixmap (enable_pic, enable_mask), self.enable_queuing_process)
		toolbar.append_space ()
		toolbar.append_item ("", _("Clear Queue"), _("Clear Queue"),
			gtk.GtkPixmap (clear_pic, clear_mask),
			self.clear_queue)
		toolbar.append_space ()
		toolbar.append_item ("", _("Load Queue"), _("Load Queue"),
			gtk.GtkPixmap (load_pic, load_mask),
			self.load_queue)
		toolbar.append_item ("", _("Save Queue"), _("Save Queue"),
			gtk.GtkPixmap (save_pic, save_mask),
			self.save_queue)

		win = gtk.GtkScrolledWindow ()
		win.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		win.show ()
		self.page.pack_start (win)

		# Create the list
		self.list = gtk.GtkCList (len (self.headings), self.headings)
		self.list.connect ("select_row", self.select_items)
		self.list.set_column_width (0, 80)
		self.list.set_column_width (1, 150)
		self.list.set_column_width (2, 80)
		self.list.set_column_width (3, 60)
		# Make the invisible ones truly invisible
		for i in range (4,12):
			self.list.set_column_visibility (i, 0)
		self.list.set_selection_mode (GTK.SELECTION_MULTIPLE)
		self.list.show ()
		win.add_with_viewport (self.list)

	def enable_queuing_process (self, button):
		"""Toggles pausing/enabling the queuing process. All entries simply wait in
		the queue while the process is disabled."""

		if not self.disabled:
			self.disabled = 1
		else:
			self.disabled = 0

	def add_to_queue (self, hash):
		"""Adds a single file with attributes in dictionary 'hash' into the queue
		and updates the status bar accordingly."""

		self.list.append ((hash['server'],
		                   hash['file'],
		                   str (hash['size']),
		                   "%s/%s" %(hash['attempt'], hash['maxattempts']),
		                   str (hash['direction']),
		                   str (hash['port']),
		                   hash['username'],
		                   hash['password'],
		                   hash['remote_path'],
		                   hash['local_path'],
		                   hash['flags'],
		                   str (hash['method'])))
		self.ctrlwin.status_queued_add (self.list.rows)
		if not self.disabled:
			self.ctrlwin.threads_tab.check_if_available_threads ()

	def get_next (self):
		"""Returns a tuple containing the data in the next entry of the list."""

		if not self.length ():
			return None

		# Build the first row entry
		entry = [ ]
		for i in range (self.list.columns):
			entry.append (self.list.get_text (0, i))
		return tuple (entry)

	def remove_next (self):
		"""Removes the next queued entry from the list, signaling that the entry
		is being processed."""

		self.list.freeze ()
		self.list.remove (0)
		self.list.thaw ()
		self.ctrlwin.status_queued_remove (self.list.rows)

	def length (self):
		"""Returns the number of entries in the list."""

		# Check if we're disabled and return a zero length to calling
		# functions will think we're empty
		if self.disabled:
			return 0
		else:
			return self.list.rows

	def clear_queue (self, button):
		"""Purge all entries from the queue."""

		self.list.clear ()
		self.ctrlwin.status_queue_clear ()

	def save_queue (self, button):
		"""Saves the contents of the queue to a file, given in the
		pop-up file selection box."""

		path = GtkExtra.file_sel_box (title=_("ftpcube - Save queue file"))
		if path is None:
			return

		try:
			file = open (path, 'w')
		except IOError, strerror:
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ] ))
			return

		for r in range (self.list.rows):
			entry = [ ]
			for c in range (self.list.columns):
				entry.append (self.list.get_text (r, c))
			entry = string.join (entry, '|')
			file.write ("%s\n" %entry)
		file.close ()

	def load_queue (self, button):
		"""Loads queue entries from a queuing file, provided by the
		user in a pop-up file selection box."""

		path = GtkExtra.file_sel_box (_("ftpcube - Load queue file"))
		if path is None:
			return

		try:
			file = open (path, "r")
		except IOError, strerror:
			GtkExtra.message_box (title=_("ftpcube Error"), message=_("ftpcube Error: %s") %strerror, buttons=( [ _("Ok") ] ))
			return

		line = file.readline ()
		while line:
			entry = string.split (line, '|')
			self.list.append (list (entry))
			line = file.readline ()
		file.close ()

	def select_items (self, list, r, c, event):
		pass

	def get_page (self):
		return self.page

class ThreadsTab:

	"""The threading tab object

	The threading tab object manages the actions of the download threads and displays
	their status. A spin button is displayed to allow the user to select the maximum
	number of threads to use for transferring and another indicates whether
	multi-threaded transferring should be used at all.

	The thread tab implements an idle function which performs updates on the
	status of each thread at specified intervals. The idle function determines
	if a thread is free and feeds it an item from the queue, and also updates idle
	counts."""

	def __init__ (self, ctrl):
		"""Creates a thread tab object and uses 'ctrl' as a container for the thread
		status display. Also sets up the idle timer to execute the idle function every
		second to update thread status and check if further processing is needed."""

		# Initialize instance variables
		self.threads = [ ]
		self.main_thread = None
		self.main_transfer = 0
		self.ctrlwin = ctrl

		self.page = gtk.GtkVBox ()
		self.page.show ()

		box = gtk.GtkHBox ()
		box.show ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)
		toolbar.set_space_size (10)
		toolbar.set_space_style (GTK.TOOLBAR_SPACE_LINE)
		toolbar.show ()
		box.pack_start (toolbar, padding=6)
		self.page.pack_start (box, expand=gtk.FALSE, padding=4)

		box = gtk.GtkHBox ()
		toolbar.append_widget (box, "", "")
		box.show ()
		self.no_threads = gtk.GtkSpinButton (gtk.GtkAdjustment (0, 0, 16, 1, 10, 0), 1, 0)
		self.no_threads.set_numeric (gtk.TRUE)
		self.no_threads.show ()
		box.pack_start (self.no_threads)
		label = gtk.GtkLabel (_("max threads"))
		label.show ()
		box.pack_start (label)

		toolbar.append_space ()
		pic, mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "main_thread.xpm"))
		toolbar.append_element (GTK.TOOLBAR_CHILD_TOGGLEBUTTON, None,
			"", _("Only use main thread for transfers"),
			_("Only use main thread for transfers"),
			gtk.GtkPixmap (pic, mask),
			self.use_main_thread)

		self.transfer_label = gtk.GtkLabel ()
		self.update_transfer_totals (0, 0.0)
		self.transfer_label.set_line_wrap (gtk.TRUE)
		self.transfer_label.show ()
		self.page.pack_start (self.transfer_label, expand=gtk.FALSE)

		# Create the page as a scrollable window so multiple threads don't
		# spill outside the screen
		vbox = gtk.GtkVBox ()
		vbox.show ()
		hbox = gtk.GtkHBox ()
		hbox.show ()
		win = gtk.GtkScrolledWindow ()
		win.set_policy (GTK.POLICY_NEVER, GTK.POLICY_AUTOMATIC)
		win.show ()
		# So we can still have the 'frame' look
		hbox.pack_start (win, padding=10)
		vbox.pack_start (hbox, padding=10)
		self.page.pack_start (vbox)

		# Create the scrolled viewport
		viewport = gtk.GtkViewport ()
		viewport.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		viewport.show ()
		win.add (viewport)

		# Create the packer for the thread objects
		self.thread_box = gtk.GtkPacker ()
		self.thread_box.show ()
		viewport.add (self.thread_box)

		# Add our idle counting function
		gtk.timeout_add (1000, self.idle_count)

	def set_max_threads (self, max):
		self.no_threads.set_value (max)

	def get_max_threads (self):
		return int (self.no_threads.get_value ())

	def update_transfer_totals (self, threads, speed):
		"""Updates the status of the current transfer speed for 'threads' number of threads.
		'speed' is in Kb."""

		self.transfer_label.set_text (_("%d Threads running, total transfer speed is %.02f Kb/s") %(threads, speed))

	def attach_main_thread (self, thread):
		"""Attachs the main thread object into the thread list."""

		self.main_thread = thread
		self.thread_box.add (thread.get_entry (), anchor=GTK.ANCHOR_N, options=GTK.FILL_X+GTK.FILL_Y)

	def use_main_thread (self, button):
		"""Updates the boolean flag indicated whether the main thread should be used for
		transfers instead of subsequent child threads."""

		# Set/unset the flag in one fell swoop
		self.main_transfer = (self.main_transfer + 1) %2

	def check_if_available_threads (self):
		"""Check if there are threads availablef or processing a queue entry (meaning they
		are idling or the thread count does not exceed the maximum). If a thread is
		available, assign it some work."""

		thread_entry = None

		server, file, size, attempt, direction, port, username, password, \
		remote_path, local_path, flags, method  = self.ctrlwin.queue_tab.get_next ()

		# Should we use the main thread or child threads?
		if self.main_transfer:
			if not self.main_thread.busy ():
				# Check if this file is on the same host. Otherwise, spawn a
				# transfer thread, since we don't really care about having multiple
				# connections to different hosts
				if self.main_thread.get_host () == server and \
				   self.main_thread.get_port () == int (port):
					thread_entry = self.main_thread
				else:
					thread_entry = self.create_thread (server, int (port), username, password)
		else:
			# First check if there's an idling thread
			found = 0

			for t in self.threads:
				if not t.busy ():
					found = 1
					# Check if we're still connected to the same host. If not, cancel
					# the thread and create a new one connected to the right host
					if t.get_host () == server and t.get_port () == int (port):
						thread_entry = t
					else:
						t.cancel_thread ()
						thread_entry = self.create_thread (server, int (port), username, password)
					break

			if not found:
				if len (self.threads) < self.no_threads.get_value ():
					thread_entry = self.create_thread (server, int (port), username, password)

		if thread_entry is not None:
			self.ctrlwin.queue_tab.remove_next ()
			cur_attempt, max_attempt = string.split (attempt, '/')

			if flags[0] == 'f':
				thread_entry.initiate_transfer (remote_path, local_path, file, int (size), int (direction), cur_attempt, int (method))
			elif flags[0] == 'd':
				thread_entry.recurse_directory (remote_path, local_path, file, int (size), int (direction), cur_attempt, int (method))
			else:
				thread_entry.recurse_link (remote_path, local_path, file, int (size), int (direction), cur_attempt, int (method))

	def create_thread (self, server, port, username, password):
		"""Creates a child transfer thread that connects to 'server' at 'port'
		with 'username':'password'."""

		# Use our app as default opts. Iterate because it's not
		# a true hash.
		opts = { }
		for key in main.app.keys ():
			opts[key] = main.app[key]

		opts['host'] = server
		opts['port'] = port
		opts['username'] = username
		opts['password'] = password

		thread_entry = threads.TransferThread (opts)
		self.add_thread_entry (thread_entry)
		return thread_entry

	def get_truesize (self, file, direction):
		"""Returns the size in bytes of 'file' from the appropriate file window,
		indicated by 'direction' which can be { ftp.DOWNLOAD, ftp.UPLOAD }."""

		if direction == ftp.DOWNLOAD:
			return main.app.remotewin.get_file_size (file)
		elif direction == ftp.UPLOAD:
			return main.app.localwin.get_file_size (file)
		return None

	def add_thread_entry (self, entry):
		"""Adds a thread entry to the thread list and displays its status."""

		self.threads.append (entry)
		self.thread_box.add (entry.get_entry (), anchor=GTK.ANCHOR_N, options=GTK.FILL_X+GTK.FILL_Y)

	def idle_count (self):
		"""Updates the idle count for threads that are not currently busy and
		checks to see if there are threads available to process queued items.
		Also updates the total transfer speed estimate."""

		total_rate = 0.0
		for t in self.threads:
			if not t.busy ():
				t.update_idle ()
				if t.finished ():
					self.thread_box.remove (t.get_entry ())
					self.threads.remove (t)
			elif t.isTransfering ():
				t.update_transfer_estimate ()
				total_rate = total_rate + t.transfer_speed ()

		# First check if there's remaining in the queue
		# If so, feed it into the threads
		if self.ctrlwin.queue_tab.length ():
			self.check_if_available_threads ()

		no_threads = len (self.threads)

		# Now check the main thread
		if self.main_thread is not None:
			# Count the main thread
			no_threads = no_threads + 1

			if self.main_thread.finished ():
				self.thread_box.remove (self.main_thread.get_entry ())
				# Set the thread references to None
				self.main_thread = None
				main.app.main_thread = None
			elif not self.main_thread.busy ():
				self.main_thread.update_idle ()
			elif self.main_thread.isTransfering ():
				self.main_thread.update_transfer_estimate ()
				total_rate = total_rate + self.main_thread.transfer_speed ()

		# Now update the totals
		self.update_transfer_totals (no_threads, total_rate)
		return gtk.TRUE

	def get_thread_list (self):
		"""Returns a list of currently active threads. If the main browsing
		thread is active, it is placed at the head of the list."""

		if self.main_thread is not None:
			thread_list = [ self.main_thread ]
			thread_list.extend (self.threads)
		else:
			thread_list = self.threads
		return thread_list

	def get_page (self):
		return self.page

class DownloadTab:

	"""The download tab object

	The tab's purpose is to display a list of past successful transfers. A spin
	button is provided indicating how many times a transfer should be attempted
	before giving up (in the event of error). Another button is provided to
	indicate the contents of a received archive."""

	status_headings = [ _('Filename'), _('Status'), _('Type'), _('Size'), _('Local Path') ]
	archive_headings = [ _('Filename'), _('Size') ]

	def __init__ (self, ctrl):
		"""Create the download tab and load the appropriate widgets. 'ctrl' is a
		reference to the parent container."""

		self.ctrlwin = ctrl

		self.page = gtk.GtkVBox ()
		self.page.show ()

		box = gtk.GtkHBox ()
		box.show ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)
		toolbar.set_space_size (10)
		toolbar.show ()
		box.pack_start (toolbar, padding=6)
		self.page.pack_start (box, expand=gtk.FALSE, padding=4)

		clear_pic, clear_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "clear.xpm"))
		archive_pic, archive_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "check_archive.xpm"))

		toolbar.append_item ("", _("Clear List"), _("Clear List"),
			gtk.GtkPixmap (clear_pic, clear_mask),
			self.clear_list)
		toolbar.append_item ("", _("Show Archive Details"), _("Show Archive Details"),
			gtk.GtkPixmap (archive_pic, archive_mask),
			self.show_archive)
		toolbar.append_space ()

		box = gtk.GtkHBox ()
		box.show ()
		self.max_attempts = gtk.GtkSpinButton (gtk.GtkAdjustment (0, 0, 16, 1, 10, 0), 1, 0)
		self.max_attempts.set_numeric (gtk.TRUE)
		self.max_attempts.show ()
		box.pack_start (self.max_attempts)
		label = gtk.GtkLabel (_("max attempts"))
		label.show ()
		box.pack_start (label, padding=4)
		toolbar.append_widget (box, "", "")

		win = gtk.GtkScrolledWindow ()
		win.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		win.show ()
		self.page.pack_start (win)

		self.status_list = gtk.GtkCList (len (self.status_headings), self.status_headings)
		self.status_list.connect ("select_row", self.select_status_items)
		for i in range (len (self.status_headings)):
			self.status_list.set_column_width (i, 75)
		# Explicitly make the filename column bigger
		self.status_list.set_column_width (0, 125)
		self.status_list.set_column_visibility (4, 0)
		self.status_list.set_selection_mode (GTK.SELECTION_MULTIPLE)
		self.status_list.show ()
		win.add_with_viewport (self.status_list)

		win = gtk.GtkScrolledWindow ()
		win.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		win.set_usize (-1, 200)
		win.show ()
		self.page.pack_start (win, expand=gtk.FALSE)

		self.archive_list = gtk.GtkCList (len (self.archive_headings), self.archive_headings)
		self.archive_list.connect ("select_row", self.select_archive_items)
		self.archive_list.set_column_width (0, 150)
		self.archive_list.set_selection_mode (GTK.SELECTION_MULTIPLE)
		self.archive_list.show ()
		win.add_with_viewport (self.archive_list)

	def set_max_attempts (self, attempts):
		self.max_attempts.set_value (attempts)

	def get_max_attempts (self):
		return int (self.max_attempts.get_value ())

	def clear_list (self, button):
		self.status_list.clear ()

	def show_archive (self, button):
		pass

	def select_status_items (self, list, r, c, event):
		pass

	def select_archive_items (self, list, r, c, event):
		pass

	def add_to_list (self, file):
		"""Add a downloaded file to the file list."""

		(st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid, st_size,
		 st_atime, st_mtime, st_ctime) = os.stat (file)
		size = app.beautify_size (st_size)

		# Note that file is a full pathname so let's split it for the list
		path, filename = os.path.split (file)

		# For now, just put status and type to "OK" and "Unknown"
		# Will need to fix later
		self.status_list.append ( (filename, _("OK"), _("Unknown"), size, path) )

	def get_page (self):
		return self.page

class FailureTab:

	"""The failure tab object

	Displays a list of failure attempts during file transfers and their causes.
	The tab provides functionality that enables the user to resubmit failed jobs
	to the queue for another attempt."""

	headings = [ _('Server'), _('Filename'), _('Size'), _('Error Msg'), _('Direction'), _('Port'), _('Username'),
	             _('Password'), _('Remote Path'), _('Local Path'), _('Attempt'), _('Type'), _('Method') ]

	def __init__ (self, ctrl):
		"""Create the download tab and load the appropriate widgets. 'ctrl' is a
		reference to the parent container object."""

		self.ctrlwin = ctrl

		self.page = gtk.GtkVBox ()
		self.page.show ()

		box = gtk.GtkHBox ()
		box.show ()
		toolbar = gtk.GtkToolbar (gtk.ORIENTATION_HORIZONTAL, gtk.TOOLBAR_ICONS)
		toolbar.set_space_size (10)
		toolbar.show ()
		box.pack_start (toolbar, padding=6)
		self.page.pack_start (box, expand=gtk.FALSE, padding=4)

		clear_pic, clear_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "clear.xpm"))
		resubmit_pic, resubmit_mask = gtk.create_pixmap_from_xpm (toolbar, None, os.path.join (main.ICONS_PREFIX, "resubmit_job.xpm"))

		toolbar.append_item ("", _("Clear List"), _("Clear List"),
			gtk.GtkPixmap (clear_pic, clear_mask),
			self.clear_list)
		toolbar.append_item ("", _("Resubmit Job"), _("Resubmit Job"),
			gtk.GtkPixmap (resubmit_pic, resubmit_mask),
			self.resubmit_job)

		win = gtk.GtkScrolledWindow ()
		win.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
		win.show ()
		self.page.pack_start (win)

		self.list = gtk.GtkCList (len (self.headings), self.headings)
		self.list.set_column_width (0, 80)
		self.list.set_column_width (1, 100)
		self.list.set_column_width (2, 80)
		self.list.set_column_width (3, 500)
		# Make the invisible, well, invisible
		for i in range (4, 13):
			self.list.set_column_visibility (i, 0)
		self.list.set_selection_mode (GTK.SELECTION_MULTIPLE)
		self.list.show ()
		win.add_with_viewport (self.list)

	def clear_list (self, button):
		self.list.clear ()

	def resubmit_job (self, button):
		"""Resubmits the selected job into the queue for another attempt at
		downloading. The attempt number is incremented appropriately."""

		max_attempts = self.ctrlwin.download_tab.get_max_attempts ()

		for i in self.list.selection:

			server, file, size, error, direction, port, username, password, \
			remote_path, local_path, attempt, type, method = self.get_list_entry (i)
			attempt = int (attempt)

			# Do nothing if we've exceeded max attempts
			if attempt > max_attempts:
				continue

			self.list.remove (i)
			self.ctrlwin.queue_tab.add_to_queue ({
				'file'        : file,
				'server'      : server,
				'port'        : port,
				'username'    : username,
				'password'    : password,
				'remote_path' : remote_path,
				'local_path'  : local_path,
				'size'        : size,
				'attempt'     : str (attempt + 1),
				'maxattempts' : max_attempts,
				'direction'   : direction,
				'flags'       : type,
				'method'      : method,
			})

	def get_list_entry (self, pos):
		"""Returns a tuple containing the data in the failure list at row
		'pos'."""

		entry = [ ]
		for i in range (self.list.columns):
			entry.append (self.list.get_text (pos, i))
		return tuple (entry)

	def add_to_list (self, hash):
		"""Adds an entry to the failure list, a transfer attempt whose attributes are
		specified in the dictionary 'hash'."""

		# Check if we've hit max attempts
		attempt = int (hash['attempt'])
		max_attempt = self.ctrlwin.download_tab.get_max_attempts ()
		if attempt > max_attempt:
			hash['error'] = _("Max Attempts Exceeded")

		self.list.append ((hash['server'], hash['file'], hash['size'], hash['error'], hash['direction'],
		                   hash['port'], hash['username'], hash['password'], hash['remote_path'],
		                   hash['local_path'], hash['attempt'], hash['type'], hash['method']))

	def get_page (self):
		return self.page
