#
# 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, GtkExtra
import time, os
import main, ftp

class MainThread:

	def __init__ (self):

		# Initialize instance variables
		self.connect = None
		self.pixmap = None
		self.label = None
		self.progress = (0, 0)
		self.idle = 0
		self.done = 0

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

		self.load_pixmaps ()

		table = gtk.GtkTable (2, 4)
		table.show ()
		self.entry_box.pack_start (table, expand=gtk.FALSE, fill=gtk.FALSE)
		# Button and label
		self.pixmap = gtk.GtkPixmap (self.connect_pixmap, self.connect_mask)
		self.pixmap.set_usize (20, 20)
		self.pixmap.show ()
		table.attach (self.pixmap, 0, 1, 1, 2, xoptions=GTK.EXPAND, yoptions=GTK.EXPAND, xpadding=2)
		self.label = gtk.GtkLabel ()
		self.label.set_line_wrap (gtk.TRUE)
		self.label.set_usize (200, -1)
		self.label.show ()
		table.attach (self.label, 1, 2, 0, 3, xoptions=GTK.EXPAND, yoptions=GTK.EXPAND, xpadding=2)

		separator = gtk.GtkHSeparator ()
		separator.show ()
		table.attach (separator, 0, 2, 3, 4)

	def load_pixmaps (self):

		self.idle_pixmap, self.idle_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_idle.xpm")
		self.connect_pixmap, self.connect_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_connect.xpm")
		self.listing_pixmap, self.listing_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_listing.xpm")
		self.abort_pixmap, self.abort_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_abort.xpm")
		self.action_pixmap, self.action_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_action.xpm")

	def initiate_connect (self):

		self.pixmap.set (self.connect_pixmap, self.connect_mask)
		self.label.set_text ("[MainThread] Host: %s\nConnecting..." %(main.app['host']))
		self.connect = ftp.FTP ()
		self.connect.get_welcome_msg ()
		self.connect.perform_login ()

		if not main.app['remotedir']:
			main.app['remotedir'] = '/'
		self.connect.cwd (main.app['remotedir'])
		main.app.update_remote_status_dir (main.app['remotedir'])
		self.connect.list ()

	def abort (self):

		self.label.set_text ("[MainThread] Host: %s\nAborting...\n" %(main.app['host']))
		self.pixmap.set (self.abort_pixmap, self.abort_mask)
		self.connect.abort ()

	def cancel_thread (self):

		if self.busy ():
			self.abort ()
		self.done = 1

	def finished (self):

		if not self.done:
			return self.done
		else:
			self.connect.destroy ()
			return self.done

	def list (self):

		self.label.set_text ("[MainThread] Host: %s\nGetting Dir Listing for %s\n"
		                     %(main.app['host'], main.app.get_remote_dir ()))
		self.pixmap.set (self.listing_pixmap, self.listing_mask)
		self.connect.list ()

	def delete (self, file):

		self.label.set_text ("[MainThread] Host: %s\nDeleting File...\n" %(main.app['host']))
		self.pixmap.set (self.action_pixmap, self.action_mask)
		self.connect.delete (file)

	def rmdir (self, file):

		self.label.set_text ("[MainTHread] Host: %s\nRemoving Directory...\n" %(main.app['host']))
		self.pixmap.set (self.action_pixmap, self.action_mask)
		self.connect.rmdir (file)

	def cwd (self, dir):

		self.label.set_text ("[MainThread] Host: %s\nChanging Directory...\n" %(main.app['host']))
		self.pixmap.set (self.action_pixmap, self.action_mask)
		self.connect.cwd (dir)

	def update_idle (self):

		self.idle = self.idle + 1
		self.label.set_text ("[MainThread] Host: %s\nIdle for %d secs" %(main.app['host'], self.idle))
		self.pixmap.set (self.idle_pixmap, self.idle_mask)

	def busy (self):
		return self.connect.ftp_busy ()

	def get_entry (self):
		return self.entry_box

class TransferThread:

	def __init__ (self):

		# Initialize instance variables
		self.connect = None
		self.progress = (0, 0)
		self.start_time = 0.0
		self.idle = 0
		self.done = 0
		self.filename = None
		self.direction = None
		self.idle_disabled = 0

		# Widget stuff
		progressbar = None
		label = None
		button = None
		separator = None
		entry_box = None

		# Create an event box to catch all button presses on the lower widgets
		self.entry_box = gtk.GtkEventBox ()
		self.entry_box.connect ("button_press_event", self.popup_menu)
		self.entry_box.show ()

		self.load_pixmaps ()

		vbox = gtk.GtkVBox ()
		vbox.show ()
		self.entry_box.add (vbox)

		self.progress_packer = gtk.GtkPacker ()
		self.progressbar = gtk.GtkProgressBar ()
		self.progressbar.set_usize (200, 10)
		self.progress_packer.add (self.progressbar, options=GTK.FILL, pad_y=4)
		vbox.pack_start (self.progress_packer)

		table = gtk.GtkTable (2, 4)
		table.show ()
		vbox.pack_start (table, expand=gtk.FALSE, fill=gtk.FALSE)
		# Button and label
		self.pixmap = gtk.GtkPixmap (self.connect_pixmap, self.connect_mask)
		self.pixmap.set_usize (20, 20)
		self.pixmap.show ()
		table.attach (self.pixmap, 0, 1, 1, 2, xoptions=GTK.EXPAND, yoptions=GTK.EXPAND, xpadding=2)
		self.label = gtk.GtkLabel ()
		self.label.set_line_wrap (gtk.TRUE)
		self.label.set_usize (200, -1)
		self.label.show ()
		table.attach (self.label, 1, 2, 0, 3, xoptions=GTK.EXPAND, yoptions=GTK.EXPAND, xpadding=2)

		separator = gtk.GtkHSeparator ()
		separator.show ()
		table.attach (separator, 0, 2, 3, 4)

		self.initiate_connect ()

	def load_pixmaps (self):

		self.idle_pixmap, self.idle_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_idle.xpm")
		self.connect_pixmap, self.connect_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_connect.xpm")
		self.listing_pixmap, self.listing_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_listing.xpm")
		self.abort_pixmap, self.abort_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_abort.xpm")
		self.donwload_pixmap, self.download_mask = gtk.create_pixmap_from_xpm (self.entry_box, None, main.ICONS_PREFIX + os.sep + "thread_download.xpm")

	def popup_menu (self, widget, event):

		if event.button == 3:
			opts = [
				( '/Cancel Download', None, self.cancel_download),
				( '/Cancel Thread', None, self.cancel_thread),
				( '/<separator>', None, None),
			]

			if not self.idle_disabled:
				opts.append (( '/Disable Idle Timeout', None, self.disable_idle_timeout))
			else:
				opts.append (( '/Enable Idle Timeout', None, self.enable_idle_timeout))

			menu = GtkExtra.MenuFactory (GtkExtra.MENU_FACTORY_MENU)
			menu.add_entries (opts)
			menu.popup (None, None, None, event.button, event.time)
			menu.show ()

	def cancel_download (self, button):

		self.label.set_text ("[Host: %s\nCancelling download...\n" %(main.app['host']))
		self.pixmap.set (self.abort_pixmap, self.abort_mask)
		self.connect.abort ()

	def cancel_thread (self, button):

		if self.busy ():
			self.cancel_download (button)
		self.done = 1

	def disable_idle_timeout (self, button):
		self.idle_disabled = 1

	def enable_idle_timeout (self, button):

		self.idle = 0
		self.idle_disabled = 0

	def initiate_connect (self):

		self.label.set_text ("Host: %s\nConnecting..." %(main.app['host']))
		self.pixmap.set (self.connect_pixmap, self.connect_mask)
		self.connect = ftp.FTP (silence=1)
		self.connect.get_welcome_msg ()
		self.connect.perform_login ()

	def initiate_transfer (self, remote_dir, local_dir, file, size, direction):

		self.connect.cwd (remote_dir)
		self.progress = (0, size)
		self.progressbar.update (0.0)
		self.start_time = time.time ()
		self.filename = file
		self.direction = direction
		self.update_transfer_estimate ()
		if self.direction == ftp.DOWNLOAD:
			self.connect.retrieve_binary (file, local_dir,  self)
		elif self.direction == ftp.UPLOAD:
			self.connect.upload_binary (file, local_dir, self)

	def recurse_directory (self, remote_dir, local_dir, file, size, direction):

		self.pixmap.set (self.listing_pixmap, self.listing_mask)

		if remote_dir == '/':
			remote_path = remote_dir + file
		else:
			remote_path = "%s/%s" %(remote_dir, file)

		if local_dir == '/':
			local_path = local_dir + file
		else:
			local_path = "%s/%s" %(local_dir, file)

		if direction == ftp.DOWNLOAD:
			if not os.path.exists (local_path):
				try:
					os.makedirs (local_path)
				except OSError, (errno, strerror):
					print "Unable to create directory: %s" %strerror
					return

			self.connect.cwd (remote_path)

			def callback (listing, remote_path=remote_path, local_path=local_path, direction=direction, self=self):

				files = [ ]
				for file in listing:
					gtk.threads_enter ()
					entry = main.app.remotewin.parse_list_entry (file)
					gtk.threads_leave ()

					if entry is not None:
						entry = list (entry)
						# Fix up the flags
						if not entry[3][0] == '-':
							entry[3] = entry [3][0]
						else:
							entry[3] = 'f'
						# Copy over the size into the truesize entry since
						# we don't care about mangling the size in the thread
						entry.append (entry[1])
						files.append (tuple (entry))

				gtk.threads_enter ()
				main.app.transfer (remote_path, local_path, files, direction)
				gtk.threads_leave ()

			# Retreive the new listing and execute the callback
			self.connect.list (callback=callback)
		else:
			self.connect.mkdir (remote_path)
			files = main.app.localwin.read_dir (local_path)
			# Purge the '..' entry
			for f in files:
				if f[0] == '..':
					files.remove (f)
			main.app.transfer (remote_path, local_path, files, direction)

	def recurse_link (self, remote_dir, local_dir, file, size, direction):

		def callback (errmsg, remote_dir=remote_dir, local_dir=local_dir, file=file, size=size, direction=direction, self=self):

			self.connect.reset ()
			self.recurse_directory (remote_dir, local_dir, file, size, direction)
			self.connect.clear_exception_handler ()

		# Set the callback handler in case this really wasn't really a directory
		self.connect.set_exception_handler (callback)
		self.initiate_transfer (remote_dir, local_dir, file, size, direction)

	def update_idle (self):

		if not self.idle_disabled:
			self.idle = self.idle + 1
		if self.idle >= main.app['thread_idle']:
			self.done = 1

		if self.progressbar['visible']:
			self.progressbar.hide ()
			self.progress_packer.hide ()
		self.pixmap.set (self.idle_pixmap, self.idle_mask)
		self.label.set_text ("Host: %s\nIdle for %d out of %d secs" %(main.app['host'], self.idle, main.app['thread_idle']))

	def update_transfer (self, amt):

		if not self.progressbar['visible']:
			self.progress_packer.show ()
			self.progressbar.show ()
			self.pixmap.set (self.donwload_pixmap, self.download_mask)
		self.idle = 0

		current, total = self.progress
		self.progress = (current + amt, total)
		percentage = float (self.progress[0]) / float (self.progress[1])
		# If the percentage is great than 1.0, such as in the case of a link
		# where we don't know the real size, set it to 1.0
		if percentage > 1.0:
			percentage = 1.0
		self.progressbar.update (percentage)

	def update_transfer_estimate (self):

		# Make the necessary calculations to create the status label
		rate = self.transfer_speed ()
		if rate:
			remaining = float (self.progress[1] - self.progress[0]) / rate
		else:
			remaining = 0
		hrs = int (remaining) / 3600
		min = (remaining % 3600) / 60
		sec = remaining % 60

		if self.direction == ftp.DOWNLOAD:
			self.label.set_text ("Host: %s\nDownloading %s with %.02f kb/s, %d of %d bytes, %d:%2d:%2d sec left"
			                     %(main.app['host'], self.filename, rate, self.progress[0],
			                       self.progress[1], hrs, min, sec))
		elif self.direction == ftp.UPLOAD:
			self.label.set_text ("Host: %s\nUploading %s with %.02f kb/s, %d of %d bytes, %d:%2d:%2d sec left"
			                     %(main.app['host'], self.filename, rate, self.progress[0],
			                       self.progress[1], hrs, min, sec))

	def isTransfering (self):

		current, total = self.progress
		if current == 0 or current == total:
			return 0
		else:
			return 1

	def transfer_speed (self):

		elapsed = time.time () - self.start_time
		if elapsed:
			return (float (self.progress[0]) / float (elapsed)) / 1024 # In KB
		else:
			return 0

	def busy (self):
		return self.connect.ftp_busy ()

	def finished (self):

		if not self.done:
			return self.done
		else:
			self.connect.destroy ()
			return self.done

	def get_entry (self):
		return self.entry_box
