#
# 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, GTK
import time, os.path
import main, app, ftp, threads

class CtrlWin:

	notebook = None

	queue_tab = None
	threads_tab = None
	download_tab = None
	failure_tab = None

	def __init__ (self, container):

		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.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 add_to_queue (self, remote_path, local_path, files, server, maxattempts, direction):

		for name, size, date, flags, truesize in files:
			self.queue_tab.add_to_queue (server, remote_path, local_path, name, truesize, 1, maxattempts, direction, flags)

	def add_to_downloads (self, file):
		self.download_tab.add_to_list (file)

	def attach_main_thread (self, thread):
		self.threads_tab.attach_main_thread (thread)

class QueueTab:

	ctrlwin = None
	page = None
	list = None

	headings = [ 'Host', 'Filename', 'Size', 'Attempt', 'Direction', 'Remote Path', 'Local Path', 'Type' ]

	def __init__ (self, ctrl):

		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)

		pic1, mask1 = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "test.xpm")
		clear_pic, clear_mask = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "clear.xpm")
		load_pic, load_mask = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "load_queue.xpm")
		save_pic, save_mask = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "save_queue.xpm")

		toolbar.append_item ("", "Enable Queuing Process", "Enable Queuing Process",
			gtk.GtkPixmap (pic1, mask1),
			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)

		self.list = gtk.GtkCList (8, 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
		self.list.set_column_visibility (4, 0)
		self.list.set_column_visibility (5, 0)
		self.list.set_column_visibility (6, 0)
		self.list.set_selection_mode (GTK.SELECTION_MULTIPLE)
		self.list.show ()
		win.add_with_viewport (self.list)

	def enable_queuing_process (self, button):
		pass

	def add_to_queue (self, host, remote_path, local_path, file, size, attempt, maxattempts, direction, flags):

		self.list.append ((host, file, "%s" %size, "%s/%s" %(attempt, maxattempts), "%s" %direction, remote_path, local_path, flags))
		self.ctrlwin.threads_tab.check_if_available_threads ()

	def get_next (self):

		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))
		self.list.freeze ()
		self.list.remove (0)
		self.list.thaw ()
		return tuple (entry)

	def length (self):
		return self.list.rows

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

	def save_queue (self, button):
		pass

	def load_queue (self, button):
		pass

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

	def get_page (self):
		return self.page

class ThreadsTab:

	ctrlwin = None
	page = None
	no_threads = None
	transfer_label = None
	thread_box = None

	main_thread = None
	threads = [ ]

	# Main thread status
	main_status_button = None
	main_label = None

	def __init__ (self, ctrl):

		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.set_value (3.0)
		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, main.ICONS_PREFIX + os.sep + "test.xpm")
		toolbar.append_item ("", "Use main thread for transfers but return when finished",
			"Use main thread for transfers but return when finished",
			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)

		frame = gtk.GtkFrame ()
		frame.set_border_width (10)
		frame.set_shadow_type (gtk.SHADOW_ETCHED_OUT)
		frame.show ()
		self.page.pack_start (frame)
		self.thread_box = gtk.GtkPacker ()
		self.thread_box.show ()
		frame.add (self.thread_box)

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

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

	def attach_main_thread (self, thread):

		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):
		pass

	def check_if_available_threads (self):

		# First check if there's an idling thread
		found = 0
		thread_entry = None

		for t in self.threads:
			if not t.busy ():
				found = 1
				thread_entry = t
				break

		if not found:
			if len (self.threads) < self.no_threads.get_value ():
				# Create the thread entry for the transfer
				thread_entry = threads.TransferThread ()
				self.add_thread_entry (thread_entry)

		host, file, size, attempt, direction, remote_path, local_path, flags = self.ctrlwin.queue_tab.get_next ()
		if flags[0] == 'f':
			thread_entry.initiate_transfer (remote_path, local_path, file, int (size), int (direction))
		elif flags[0] == 'd':
			thread_entry.recurse_directory (remote_path, local_path, file, int (size), int (direction))
		else:
			thread_entry.recurse_link (remote_path, local_path, file, int (size), int (direction))

	def get_truesize (self, file, direction):

		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):

		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):

		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 ()

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

	def get_page (self):
		return self.page

class DownloadTab:

	ctrlwin = None
	page = None

	max_attempts = None

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

	def __init__ (self, ctrl):

		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, main.ICONS_PREFIX + os.sep + "clear.xpm")
		pic2, mask2 = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "test.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 (pic2, mask2),
			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.set_value (3.0)
		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 (5, 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 (2, 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 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):

		(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:

	ctrlwin = None
	page = None
	list = None

	headings = [ 'Server', 'Filename', 'Size', 'Error Msg', 'Remote Path' ]

	def __init__ (self, ctrl):

		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, main.ICONS_PREFIX + os.sep + "clear.xpm")
		pic2, mask2 = gtk.create_pixmap_from_xpm (toolbar, None, main.ICONS_PREFIX + os.sep + "test.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 (pic2, mask2),
			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 (4, self.headings)
		for i in range (len (self.headings)):
			self.list.set_column_width (i, 80)
		# Explicitly make the filename bigger
		self.list.set_column_width (1, 100)
		self.list.set_column_visibility (4, 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):
		pass

	def add_to_list (self, file, size, error):

		path, filename = os.path.split (file)
		server = main.app['host']
		self.list.append ( (server, file, size, error, path) )

	def get_page (self):
		return self.page
