#!/usr/bin/env python
# -*- coding: UTF-8 -*-

#Freeloader - a download manager for gnome written in python using pygtk
#Copyright (C) 2005 Steven Grafton

#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.


import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
import gtk.gdk
import gnome
import gnome.ui
import gobject
import posixpath
import os
import os.path
import sys
import thread
import threading
import BitTorrent
import time
import tempfile
import gconf
import socket
import random
import gnomevfs
import BitTorrent.bencode
import string
try:
	import egg
	import egg.trayicon
	use_tray = 1
except:
	use_tray = 0

try:
	import dbus
#import dbus_bindings
	use_dbus = 1
except:
	use_dbus = 0

eggw = eggh = 24
share_dir = "/usr/share/freeloader"
img_dir = share_dir + "/pixmaps/"
glade_dir = share_dir + "/glade/"
gconf_dir = "/apps/freeloader"
use_bus = 0
dbus_service_name = "com.ruinedsoft.Freeloader"
TOOL_TIMEOUT = 2000
QUIT_TIMEOUT = 2000
SYNC_TIMEOUT = 555

sys.path.append(share_dir)

import webdl
import btdl
import genericdl
import sockdae
#import webbtdl
#import fldservice

# (longName, shortName, type, default,flags, descrip , argDescrip)
parm_table = [
("show-window",	"s", None, None, 0, "Show the Main Window", ""),
("hide-window",	"h", None, None, 0, "Hide the Main Window", ""),
("stop-all",	"x", None, None, 0, "Stop all active transfers", ""),
("quit",	"q", None, None, 0, "Tell program to quit", "")]

class Freeloader(object):
#class Freeloader(dbus.Object):
	#def __init__(self, service, files):
	#	dbus.Object.__init__(self, "/Freeloader", service,
	#				[self.ping, self.start_torrent,
	#				self.start_url])

	def __init__(self, files):
		self.killflag = threading.Event()
		self.error_flag = 0
		self.map_glade()
		self.setup_dnd()
		self.do_gconf()
		gobject.timeout_add(TOOL_TIMEOUT, self.tooltip_update)
		gobject.timeout_add(SYNC_TIMEOUT, self.sync_ui_timer)
		self.print_log("Finished Initialization", self.stock_info)
		self.setwindowvis(True)
		for f in files:
			self.sd.handle_file(f)

	def mon_callback(self, path, event, data = None):
		if event != gnomevfs.MONITOR_EVENT_DELETED:
			return
		#if data is None:
		#	return
		#t = self.lstore.get_value(data, 3)
		#if t is not None:
		#	t.please_exit()
		#gtk.gdk.threads_enter()
		#self.lstore.remove(data)
		#gtk.gdk.threads_leave()
		#self.gam.stop_watch(path)
		
		gtk.gdk.threads_enter()
		iter = self.lstore.get_iter_first()
		while iter:
			t = self.lstore.get_value(iter, 3)
			if t is not None:
				fpath = os.path.join(data, path)
				if cmp(t.get_fullfile(), fpath) == 0:
					self.lstore.remove(iter)
					gtk.gdk.threads_leave()
					return
			iter = self.lstore.iter_next(iter)
		gtk.gdk.threads_leave()

	def watch_file(self, path, iter):
		head,tail = os.path.split(path)
		try:
			k = self.gnomevfs.monitor_add(head, 
						gnomevfs.MONITOR_FILE,
						self.mon_callback,
						self)
			self.__monitored[name] = k
		except:
			gtk.gdk.threads_enter()
			self.print_log("Already watching: " + head)
			gtk.gdk.threads_leave()

	def watch_stop(self, path):
		self.gnomevfs.monitor_cancel(self.__monitored[name])

	def ping(self, message, param=None):
		gtk.gdk.threads_enter()
		self.print_log("Pong!", self.stock_downup)
		gtk.gdk.threads_leave()

	def on_properties(self, obj):
		(path, focus) = self.tv.get_cursor()
		if path is not None:
			iter = self.lstore.get_iter(path)
			t = self.lstore.get_value(iter, 3)
			if t.isAlive():
				self.status_label.set_text("Active")
			else :
				self.status_label.set_text("Inactive")
			self.title_label.set_markup("<big><b>" + t.get_tail() + "</b></big>")
			self.destination_label.set_text(t.get_fullfile())
			self.source_label.set_text(t.src_file)
			self.size_label.set_text(t.format(t.size_total))
			stockid = self.lstore.get_value(iter, 0)
			self.transfer_image.set_from_stock(stockid, gtk.ICON_SIZE_DIALOG)
			self.properties_dialog.run()
			self.properties_dialog.hide()
		return

	def on_tv_drag_drop(self, obj, context, x, y, time, param = None):
		#data = obj.drag_dest_find_target(context,
		#				obj.drag_dest_get_target_list())
		return True

	def on_tv_drag_data_received(self, obj, context, x, y, sel, id,
					time, param = None):

		if id == self.dnd_type_url:
			url = ''
			# fixme unicode to string hax
			for c in list(sel.data):
				l = str(c)
				if string.printable.find(l) != -1:
					url += l
			self.start_url(None, url)
		elif id == self.dnd_type_files:
			files = sel.get_uris()
			for f in files:
				try:
					pa = gnomevfs.get_local_path_from_uri(f)
					if os.path.isfile(pa):
						self.start_torrent(None, pa)
					else:
						raise
				except:
					self.print_log("Ignored URI (not local?): " + f, self.stock_error)
		context.finish(True, time)

	def setup_dnd(self):
		self.dnd_type_url = 1
		self.dnd_type_files = 2
		targets = [	("text/x-moz-url-data", 0, self.dnd_type_url),
				("text/uri-list", 0, self.dnd_type_files)]
		actions = gtk.gdk.ACTION_COPY
		self.tv.drag_dest_set(gtk.DEST_DEFAULT_ALL, targets, actions)
		global use_tray
		if use_tray == 1:
			self.applet_window.drag_dest_set(gtk.DEST_DEFAULT_ALL,
							targets, actions)

	def egg_destroy(self, obj):
		global use_tray
		use_tray = 0
		self.hide_menu.set_sensitive(False)
		self.check_shown.set_sensitive(False)
		self.setwindowvis(True)

	def map_glade(self):
		self.tooltips = gtk.Tooltips()
		gtk.window_set_default_icon_from_file(
			img_dir + 'freeloader_icon.svg')

		(self.icon_width,self.icon_height) = gtk.icon_size_lookup(
							gtk.ICON_SIZE_DND)
		(bwidth,bheight) = gtk.icon_size_lookup(gtk.ICON_SIZE_BUTTON)

		self.event_box = gtk.EventBox()
		self.stock_down = "arrows-down"
		self.add_icon(self.stock_down, img_dir + 'freeloader_down.svg')
		self.stock_up = "arrows-up"
		self.add_icon(self.stock_up, img_dir + 'freeloader_up.svg')
		self.stock_downup = "arrows-down-up"
		self.add_icon(self.stock_downup,img_dir+'freeloader_downup.svg')
		self.stock_tray = "freeloader-tray"
		self.add_icon(self.stock_tray,img_dir + 'freeloader_icon.svg')
		self.stock_tray_flash = "freeloader-tray-flash"
		self.add_icon(self.stock_tray_flash, img_dir +
				'freeloader_icon_flash.svg')

		self.stock_error = gtk.STOCK_DIALOG_ERROR
		self.stock_info = gtk.STOCK_DIALOG_INFO

		self.clipboard = gtk.Clipboard()

		self.glade = gtk.glade.XML(glade_dir + 'freeloader.glade')

		global use_tray
		if use_tray == 1:
			self.applet_window = egg.trayicon.TrayIcon(
						'Freeloader Tray')
			self.applet_window.connect('destroy', self.egg_destroy)
			self.applet_window.connect('drag-drop',
							self.on_tv_drag_drop)
			self.applet_window.connect('drag-data-received',
						self.on_tv_drag_data_received)
			self.tray_image = gtk.Image()
			#self.tray_button = gtk.ToggleButton()
			self.tray_image.set_from_stock(self.stock_tray,
							gtk.ICON_SIZE_BUTTON)
			#self.tray_button.add(self.image_widget)
			#self.event_box.add(self.tray_button)
			self.event_box.add(self.tray_image)
			self.applet_window.add(self.event_box)
			self.applet_window.show_all()
			self.event_box.connect('button-press-event',
						self.trayclicked)
			self.tray_menu =self.glade.get_widget('traycontextmenu')
			self.tray_menu.attach_to_widget(self.event_box,
							self.traydetach)
			self.tray_menu.show_all()
			self.tooltips.set_tip(self.applet_window, 'Freeloader')

		self.main_window = self.glade.get_widget('main_window')
		self.pref_window = self.glade.get_widget('pref_window')
		self.about_dialog = self.glade.get_widget('about_dialog')
		self.log_window = self.glade.get_widget('log_window')
		self.log_tv = self.glade.get_widget('log_tv')
		self.log_toggle = self.glade.get_widget('log_toggle')
		self.gen_pref_table = self.glade.get_widget('gen_pref_table')
		self.logo_image = self.glade.get_widget('logo_image')
		self.logo_label = self.glade.get_widget('logo_label')
		self.vbox = self.glade.get_widget('mainvbox')
		self.save_every_check=self.glade.get_widget('save_every_check')
		self.browse_in_button=self.glade.get_widget('browse_in_button')
		self.browse_out_button = self.glade.get_widget('browse_out_button')
		self.bt_pref_image = self.glade.get_widget('bt_pref_image')
		self.log_image = self.glade.get_widget('log_image')
		self.move_check = self.glade.get_widget('move_check')
		self.confirm_check = self.glade.get_widget('confirm_check')
		self.hide_log_check = self.glade.get_widget('hide_log_check')
		self.address_check = self.glade.get_widget('address_check')
		self.address_entry = self.glade.get_widget('address_entry')
		self.delfile_label = self.glade.get_widget('delfile_label')
		self.maxup_check = self.glade.get_widget('maxup_check')
		self.maxup_sb = self.glade.get_widget('maxup_sb')
		self.port_check = self.glade.get_widget('port_check')
		self.port_low_sb = self.glade.get_widget('port_low_sb')
		self.port_high_sb = self.glade.get_widget('port_high_sb')
		self.exec_button = self.glade.get_widget('exec_button')
		self.exec_menu = self.glade.get_widget('exec_menu')
		self.location_button = self.glade.get_widget('location_button')
		self.location_menu = self.glade.get_widget('location_menu')
		self.properties_button = self.glade.get_widget('properties_button')
		self.properties_menu = self.glade.get_widget('properties_menu')
		self.size_label = self.glade.get_widget('size_label')
		self.title_label = self.glade.get_widget('title_label')
		self.status_label = self.glade.get_widget('status_label')
		self.transfer_image = self.glade.get_widget('transfer_image')
		self.destination_label = self.glade.get_widget('destination_label')
		self.source_label = self.glade.get_widget('source_label')
		self.properties_dialog = self.glade.get_widget('properties_dialog')
		self.clean_up_button = self.glade.get_widget('clean_up_button')
		self.clean_up_menu = self.glade.get_widget('clean_up_menu')
		self.hide_menu = self.glade.get_widget('hide_menu')
		self.stop_button = self.glade.get_widget('stop_button')
		self.stop_menu = self.glade.get_widget('stop_menu')
		self.restart_button = self.glade.get_widget('restart_button')
		self.restart_menu = self.glade.get_widget('restart_menu')
		self.remove_button = self.glade.get_widget('remove_button')
		self.remove_menu = self.glade.get_widget('remove_menu')
		#self.stop_all_button = self.glade.get_widget('stop_all_button')
		self.stop_all_menu = self.glade.get_widget('stop_all_menu')
		self.stop_all_cmenu = self.glade.get_widget('stop_all_cmenu')
		
		self.incoming_entry = self.glade.get_widget('incoming_entry')
		self.move_entry = self.glade.get_widget('move_entry')
		self.url_entry = self.glade.get_widget('url_entry')
		self.restart_button = self.glade.get_widget('restart_button')
		self.remove_button = self.glade.get_widget('remove_button')
		self.url_dialog = self.glade.get_widget('url_dialog')
		self.confirm_dialog = self.glade.get_widget('confirm_dialog')
		self.tv = self.glade.get_widget('treeview')
		self.tv.set_search_column(1)
		self.tv.set_search_equal_func(self.search_match)

		self.logo_image.set_from_file(img_dir +
						'freeloader_icon_arrows.svg')
		self.logo_label.set_markup('<b><span size="xx-large">Freeloader 0.3</span></b>')

		self.check_shown = self.glade.get_widget('check_shown')

		self.logstore = gtk.ListStore(	str,
						str,
						str,
						float)
		cr = gtk.CellRendererPixbuf()
		col = gtk.TreeViewColumn('Type', cr);
		col.add_attribute(cr, 'stock-id', 0)
		col.width = self.icon_width
		col.set_clickable(True)
		col.set_sort_column_id(0)
		self.log_tv.append_column(col)

		cr = gtk.CellRendererText()
		col = gtk.TreeViewColumn('Message', cr);
		col.add_attribute(cr, 'text', 2)
		col.set_expand(True)
		self.log_tv.append_column(col)
		col.set_clickable(True)
		col.set_sort_column_id(2)
		expander = col

		cr = gtk.CellRendererText()
		col = gtk.TreeViewColumn('Time', cr);
		col.add_attribute(cr, 'text', 1)
		col.set_expand(False)
		col.set_clickable(True)
		col.set_sort_column_id(3)
		self.log_tv.append_column(col)

		self.log_tv.set_model(self.logstore)
		self.log_tv.set_expander_column(expander)
		self.log_tv.set_search_column(2)
		self.log_tv.set_search_equal_func(self.search_match_log)

		self.lstore = gtk.ListStore(	str,
						str,
						int,
						object,
						str,
						int)
		cr = gtk.CellRendererPixbuf()
		col = gtk.TreeViewColumn('Status', cr);
		col.add_attribute(cr, 'stock-id', 0)
		col.add_attribute(cr, 'stock-size', 5)
		col.width = self.icon_width
		col.set_clickable(True)
		self.tv.append_column(col)
		col.set_sort_column_id(0)

		cr = gtk.CellRendererText()
		col = gtk.TreeViewColumn('File Information', cr);
		col.add_attribute(cr, 'markup', 1)
		col.set_expand(True)
		col.set_clickable(True)
		col.set_resizable(True)
		self.tv.append_column(col)
		expander = col
		self.file_col = col
		self.file_col.set_sort_column_id(1)

		cr = gtk.CellRendererProgress()
		col = gtk.TreeViewColumn('Progress', cr);
		col.add_attribute(cr, 'value', 2)
		col.add_attribute(cr, 'text', 4)
		col.set_resizable(False)
		col.set_clickable(True)
		col.set_expand(True)
		self.prog_col = col
		self.tv.append_column(col)
		self.prog_col.set_sort_column_id(2)

		self.tv.set_model(self.lstore)
		self.tv.set_headers_clickable(True)
		self.tv.set_expander_column(expander)
		self.theme = gtk.icon_theme_get_default()

		self.signal_autoconnect()

		self.bt_pref_image.set_from_stock(self.stock_downup,
							gtk.ICON_SIZE_BUTTON)

	def add_icon(self, stockid, file):
		if gtk.icon_factory_lookup_default(stockid) != None:
			return
		src = gtk.IconSource()
		src.set_filename(file)
		src.set_size_wildcarded(True)

		set = gtk.IconSet()
		set.add_source(src)

		fact = gtk.IconFactory()
		fact.add(stockid, set)
		fact.add_default()

	def icon_exists(self, stockid):
		return

	def gconf_init_entry(self, key, entry, default):
		fullkey = gconf_dir + "/" + key
		self.gconf_client.notify_add(	fullkey,
						self.text_key_changed,
						entry)
		try:
			s = self.gconf_client.get(fullkey)
			s.get_string()
		except:
			self.print_log("Unexpected key type for (preferred string): " + str(fullkey), self.stock_error)
			s = None
		if s is None:
			entry.set_text(default)
			self.gconf_client.set_string(fullkey, default)
		else:
			entry.set_text(s.get_string())
		entry.connect("changed", self.update_text_key, str(fullkey))

	def gconf_init_check(self, key, check, init, widgets=[]):
		fullkey = gconf_dir + "/" + key
		self.gconf_client.notify_add(	fullkey,
						self.bool_key_changed,
						(check,	widgets))
		try:
			b = self.gconf_client.get(fullkey)
			b.get_bool()
		except:
			self.print_log("Unexpected key type for (preferred bool): " + str(fullkey), self.stock_error)
			b = None
		if b is None:
			check.set_active(init)
			for w in widgets:
				w.set_sensitive(init)
			self.gconf_client.set_bool(fullkey, init)
		else:
			check.set_active(b.get_bool())
			for w in widgets:
				w.set_sensitive(b.get_bool())
		check.connect("toggled", self.update_bool_key,
				(str(fullkey), widgets))

	def gconf_init_sb(self, key, sb, init, widgets=[]):
		fullkey = gconf_dir + "/" + key
		self.gconf_client.notify_add(	fullkey,
						self.int_key_changed, sb)
		try:
			i = self.gconf_client.get(fullkey)
			i.get_int()
		except:
			self.print_log("Unexpected key type for (preferred int): " + str(fullkey), self.stock_error)
			i = None
		if i is None:
			sb.set_value(init)
			self.gconf_client.set_int(fullkey, init)
		else:
			sb.set_value(i.get_int())
		sb.connect("value_changed", self.update_int_key, str(fullkey))

	def text_key_changed(self, client, conn_id, entry, param):
		if not entry.value:
			# borked key?
			param.set_text("")
		else:
			if entry.value.type == gconf.VALUE_STRING:
				param.set_text(entry.value.to_string())
			else:
				param.set_text("")
				self.print_log("Invalid gconf value for text: " + str(entry), self.stock_error)

	def bool_key_changed(self, client, conn_id, entry, data):
		(param, widgets) = data
		if not entry.value:
			# borked key?
			#param.set_active(true or false?)
			pass
		else:
			if entry.value.type == gconf.VALUE_BOOL:
				val = entry.value.get_bool()
				param.set_active(val)
				for w in widgets:
					w.set_sensitive(val)
			else:
				#param.set_active(true or false?)
				self.print_log("Invalid gconf value for boolean: " + str(entry), self.stock_error)

	def int_key_changed(self, client, conn_id, entry, param):
		if not entry.value:
			# borked key?
			#param.set_value(6881)
			pass
		else:
			if entry.value.type == gconf.VALUE_INT:
				val = entry.value.get_int()
				param.set_value(val)
			else:
				#param.set_value(6881)
				self.print_log("Invalid gconf value for int: " + str(entry), self.stock_error)

	def update_text_key(self, entry, key):
		self.gconf_client.set_string(key, entry.get_text())

	def update_bool_key(self, check, keyandwidgets):
		(key, widgets) = keyandwidgets
		self.gconf_client.set_bool(key, check.get_active())
		for w in widgets:
			w.set_sensitive(check.get_active())

	def update_int_key(self, sb, key):
		self.gconf_client.set_int(key, sb.get_value_as_int())

	def reset_readonly(self, client, conn_id, entry, param):
		self.gconf_client.set_string(entry.get_key(), param)

	def do_gconf(self):
		self.gconf_client = gconf.client_get_default()
		self.gconf_client.add_dir(gconf_dir, gconf.CLIENT_PRELOAD_NONE)

		#enough init, do default params
		# entry (text) config
		homedir = os.path.expanduser("~/Desktop")
		if os.path.isdir(homedir) == False:
			homedir = tempfile.gettempdir()

		self.gconf_init_entry("incoming_dir", self.incoming_entry,
					tempfile.gettempdir())
		self.gconf_init_entry("move_dir", self.move_entry,
					homedir)
		self.gconf_init_entry("bt_custom_address",self.address_entry,"")

		# check (bool) config
		self.gconf_init_check("save_every", self.save_every_check,
					True, [self.incoming_entry,
							self.browse_in_button])
		self.gconf_init_check("move_completed", self.move_check,
					False, [self.move_entry,
							self.browse_out_button])
		self.gconf_init_check("confirm_delete", self.confirm_check,
					True, [])
		self.gconf_init_check("log_button_hide", self.hide_log_check,
					True, [])
		self.gconf_init_check("bt_use_custom_address",
					self.address_check,
					False, [self.address_entry])
		self.gconf_init_check("bt_custom_ports", self.port_check,
					False, [self.port_low_sb,
							self.port_high_sb])
		self.gconf_init_check("bt_max_upload_rate", self.maxup_check,
					False, [self.maxup_sb])

		# spin box (int) config
		self.gconf_init_sb("bt_min_port", self.port_low_sb, 6881)
		self.gconf_init_sb("bt_max_port", self.port_high_sb, 6999)

		# local socket connection
		random.seed()
		fullkey = gconf_dir + "/localsock"
		self.sockfd = tempfile.gettempdir() + "/" + \
				str(random.randint(10000, 99999))
		self.gconf_client.set_string(fullkey, self.sockfd)
		self.gconf_client.notify_add(	fullkey,
						self.reset_readonly,
						self.sockfd)
		self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
		self.socket.bind(self.sockfd)
		self.socket.listen(1)

		fullkey = gconf_dir + "/localsockkey"
		self.sockkey = str(random.randint(10000, 99999))
		self.gconf_client.set_string(fullkey, self.sockkey)
		self.gconf_client.notify_add(	fullkey,
						self.reset_readonly,
						self.sockkey)

		self.on_hide_log_check_toggled(self.hide_log_check) 

		self.sd = sockdae.Sockdae(self.socket, self.sockkey, self)
		self.sd.start()

	def on_hide_log_check_toggled(self, obj):
		if obj.get_active() == False:
			self.log_toggle.show()
		else:
			if self.error_flag == 0:
				self.log_toggle.hide()
				self.log_toggle.set_active(False)
				self.log_window.hide()
			else:
				self.log_toggle.show()

	def on_pref_reset(self, obj):
		homedir = os.path.expanduser("~/Desktop")
		if os.path.isdir(homedir) == False:
			homedir = tempfile.gettempdir()

		self.incoming_entry.set_text(tempfile.gettempdir())
		self.move_entry.set_text(homedir)
		self.address_entry.set_text("")
		self.save_every_check.set_active(True)
		self.move_check.set_active(False)
		self.confirm_check.set_active(True)
		self.log_hide_check.set_active(True)
		self.address_check.set_active(False)
		self.port_check.set_active(False)
		self.maxup_check.set_active(False)
		self.maxup_sb.set_value(0)
		self.port_low_sb.set_value(6881)
		self.port_high_sb.set_value(6999)

	def on_porthigh_changed(self, obj):
		high = int(self.port_high_sb.get_value())
		low = int(self.port_low_sb.get_value())
		if high < low:
			self.port_low_sb.set_value(high)

	def on_portlow_changed(self, obj):
		high = int(self.port_high_sb.get_value())
		low = int(self.port_low_sb.get_value())
		if high < low:
			self.port_high_sb.set_value(low)

	def sync_ui_timer(self):
		if self.killflag.isSet():
			return False
		gtk.gdk.threads_enter()
		if gtk.main_level() == 0:
			gtk.gdk.threads_leave()
			return False
		try:
			self.sync_ui()
		except:
			gtk.gdk.threads_leave()
			return False
		gtk.gdk.threads_leave()
		return True

	def tooltip_update(self):
		try:
			res = 'Freeloader'
			numactive = 0
			rateup = 0
			ratedown = 0
			gtk.gdk.threads_enter()
			iter = self.lstore.get_iter_first()
			while iter:
				t = self.lstore.get_value(iter, 3)
				if t:
					if t.isAlive():
						numactive += 1
						if isinstance(t, btdl.BTDL):
							rateup += t.upRate
							ratedown += t.downRate
						else:
							ratedown += t.rate
				iter = self.lstore.iter_next(iter)

			if numactive > 0:
				if numactive > 1:
					res ='%s - %d transfers\nDown: %s/s Up: %s/s' % (res, numactive, genericdl.format(ratedown), genericdl.format(rateup))
				else:
					res = '%s - %d transfer\nDown: %s/s Up: %s/s' % (res, numactive, genericdl.format(ratedown), genericdl.format(rateup))
			
			if gtk.main_level() == 0:
				gtk.gdk.threads_leave()
				return False
			self.tooltips.set_tip(self.applet_window, res)
			gtk.gdk.threads_leave()
			return True
		except:
			gtk.gdk.threads_leave()
			return False

	def on_about_activate(self, obj):
		self.about_dialog.show()
		self.about_dialog.run()
		self.about_dialog.hide()

	def on_select_dir_in(self, obj):
		fc = gtk.FileChooserDialog("Choose folder location...",
					self.main_window,
					gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
		fc.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
				gtk.STOCK_OK,	gtk.RESPONSE_ACCEPT)
		fc.set_current_folder(self.incoming_entry.get_text())
		res = fc.run()
		fc.hide()
		if res == gtk.RESPONSE_ACCEPT:
			self.incoming_entry.set_text(fc.get_filename())

	def on_select_dir_out(self, obj):
		fc = gtk.FileChooserDialog("Choose folder location...",
					self.main_window,
					gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
		fc.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
				gtk.STOCK_OK,	gtk.RESPONSE_ACCEPT)
		fc.set_current_folder(self.move_entry.get_text())
		res = fc.run()
		fc.hide()
		if res == gtk.RESPONSE_ACCEPT:
			self.move_entry.set_text(fc.get_filename())


	def search_match_log(self, model, col, key, iter, data=None):
		haystack = model.get_value(iter, 1).lower() #markup text
		res = haystack.count(key.lower())
		haystack = model.get_value(iter, 2).lower() #markup text
		res2 = haystack.count(key.lower())
		if res > 0 or res2 > 0:
			return False
		return True

	def search_match(self, model, col, key, iter, data=None):
		haystack = model.get_value(iter, 1).lower() #markup text
		res = haystack.count(key.lower())
		if res > 0:
			return False
		return True

	def clean_up_for_each(self, model, path, iter, data):
		if iter is not None:
			t = model.get_value(iter, 3)
			if t is not None:
				if not t.isAlive():
					model.remove(iter)
					data.set()
					return True

	def on_clean_up(self, obj):
		ev = threading.Event()
		ev.set()
		while(ev.isSet()):
			ev.clear()
			self.lstore.foreach(self.clean_up_for_each, ev)

	def on_remove(self, obj):
		(path, focus) = self.tv.get_cursor()
		if path is None:
			return
		
		iter = self.lstore.get_iter(path)
		t = self.lstore.get_value(iter, 3)

		if self.confirm_check.get_active() == True:
			self.delfile_label.set_text(t.get_fullfile())
			self.confirm_dialog.show()
			res = self.confirm_dialog.run()
			self.confirm_dialog.hide()
			if res != gtk.RESPONSE_ACCEPT:
				return
		self.kill_thread(self.lstore, path, iter, None)
		# do removal
		try:
			os.remove(t.get_fullfile())
		except:
			try:
				for root,dirs,files in os.walk(t.get_fullfile(),
								topdown=False):
					for name in files:
						os.remove(os.path.join(root,
									name))
					for name in dirs:
						os.rmdir(os.path.join(root,
									name))
				os.rmdir(t.get_fullfile())
			except:
				self.print_log("Problem deleting " + t.get_fullfile(), self.stock_error)
		
		self.lstore.remove(iter)
		self.sync_ui()

	def on_pref_window_close(self, obj, ev=None):
		self.pref_window.hide()
		return True

	def on_prefs_activate(self, obj):
		self.pref_window.show()

	def on_log_toggle(self, obj):
		if obj.get_active() == True:
			self.log_window.show()
			if self.error_flag == 1:
				self.error_flag = 0
				self.log_image.set_from_stock(self.stock_info,
							gtk.ICON_SIZE_MENU)
		else:
			self.log_window.hide()
			self.log_image.set_from_stock(self.stock_info,
							gtk.ICON_SIZE_MENU)

	def get_save_loc(self, sug):
		if self.save_every_check.get_active() == False:
			fc2 = gtk.FileChooserDialog(
						"Choose save location...",
						self.main_window,
						gtk.FILE_CHOOSER_ACTION_SAVE)
			fc2.add_buttons(gtk.STOCK_CANCEL,
						gtk.RESPONSE_CLOSE,
					gtk.STOCK_SAVE,
						gtk.RESPONSE_ACCEPT)
			fc2.set_current_name(sug)
			res2 = fc2.run()
			fc2.hide()
			if res2 == gtk.RESPONSE_ACCEPT:
				des = fc2.get_filename()
			else:
				des = None
		else:
			des = os.path.join(self.incoming_entry.get_text(), sug)
		return des

	def decode_name(self, torfile):
		try:
			fp = open(torfile, "rb")
			res = fp.read()
			fp.close()
			res = BitTorrent.bencode.bdecode(res)
			return res['info']['name']
		except:
			return None

	def start_torrent(self, message, src):
		if message:
			self.print_log("Torrent from dbus: " + src)
			self.print_log(message)

		torsug = self.decode_name(src)
		if torsug is None:
			h,t = os.path.split(src)
			self.print_log("Invalid Torrent file: " + t,
					self.stock_error)
			return

		des = self.get_save_loc(torsug)
		if des is None:
			return

		if self.move_check.get_active():
			final = self.move_entry.get_text()
		else:
			final = None

		row = self.add_row(self.stock_downup)

		if self.maxup_check.get_active() == True:
			up = int(self.maxup_sb.get_value())
		else:
			up = None

		if self.address_check.get_active() == True:
			addr = self.address_entry.get_text()
		else:
			addr = None

		if self.port_check.get_active() == True:
			lop = int(self.port_low_sb.get_value())
			lom = int(self.port_high_sb.get_value())
		else:
			lop = None
			lom = None

		dl = btdl.BTDL(0, 0, up, lop, lom, addr, self.lstore, row,
					src, des, final,
					self.watch_file, self.print_log)
		dl.start()

	def start_url(self, message, src):
		if message:
			self.print_log("URL from dbus: " + src)
			self.print_log(message)

		(h, tail) = posixpath.split(src)
		# todo if tail is too large, overwriting another file or
		# weird chars, make your own filename as specified by user
		des = self.get_save_loc(tail)
		if des is None:
			return

		if self.move_check.get_active():
			final = self.move_entry.get_text()
		else:
			final = None

		row = self.add_row(self.stock_down)
		dl = webdl.WebDL(self.lstore, row, src, des, final,
					self.watch_file, self.print_log)
		dl.start()

	def on_open_torrent(self, obj):
		fc = gtk.FileChooserDialog("Open BitTorrent File")
		filtertor = gtk.FileFilter()
		filtertor.set_name("Torrent Files")
		filtertor.add_pattern("*.torrent")
		filtertor.add_pattern("*.TORRENT")
		filtertor.add_pattern("*.Torrent")
		filterall = gtk.FileFilter()
		filterall.set_name("All Files")
		filterall.add_pattern("*")
		fc.add_filter(filtertor)
		fc.add_filter(filterall)
		fc.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CLOSE,
				gtk.STOCK_OPEN,	gtk.RESPONSE_ACCEPT)
		res = fc.run()
		fc.hide()
		if res == gtk.RESPONSE_ACCEPT:
			self.start_torrent(None, fc.get_filename())

	def on_url_ok(self, obj, ev=None):
		self.url_dialog.hide()
		self.start_url(None, self.url_entry.get_text())

	def on_url_cancel(self, obj, ev=None):
		self.url_dialog.hide()
		return True

	def on_open_url(self, obj):
		data = self.clipboard.wait_for_text()
		if data is not None:
			self.url_entry.set_text(data)
		self.url_dialog.show()

	def signal_autoconnect(self):
		signals = {}
		for attr_name in dir(self):
			attr = getattr(self, attr_name)
			if callable(attr):
				signals[attr_name] = attr
		self.glade.signal_autoconnect(signals)

	def on_exec_button(self, obj):
		(path, focus) = self.tv.get_cursor()
		if path is not None:
			iter = self.lstore.get_iter(path)
		else:
			return
		t = self.lstore.get_value(iter, 3)
		if os.path.exists(t.get_fullfile()):
			gnome.ui.url_show_on_screen('file://'+ t.get_fullfile(),
						gtk.gdk.screen_get_default())
	
	def on_location_button(self, obj):
		(path, focus) = self.tv.get_cursor()
		if path is not None:
			iter = self.lstore.get_iter(path)
			t = self.lstore.get_value(iter, 3)
			if os.path.exists(t.get_dir()):
				gnome.ui.url_show_on_screen('file://' +
						t.get_dir(),
						gtk.gdk.screen_get_default())

	def on_stop_button(self, obj):
		(path, focus) = self.tv.get_cursor()
		if path is not None:
			iter = self.lstore.get_iter(path)
			self.kill_thread(self.lstore, path, iter, None)
			self.sync_ui()

	def on_restart_button(self, obj):
		(mod, iter) = self.tv.get_selection().get_selected()
		if iter == None:
			return
		t = self.lstore.get_value(iter, 3)
		if t == None:
			return
		if t.isAlive():
			return

		if self.move_check.get_active():
			final = self.move_entry.get_text()
		else:
			final = None

		if isinstance(t, btdl.BTDL):
			row = self.add_row(self.stock_downup)
			up = int(self.maxup_sb.get_value())
			t2 = btdl.BTDL(	t.upTotal + t.initUp,
					t.downTotal + t.initDown,
					up, t.lowport, t.highport, t.address,
					self.lstore, row,
					t.src_file,
					t.get_fullfile(),
					final,
					self.watch_file, self.print_log)
		elif isinstance(t, webdl.WebDL):
			row = self.add_row(self.stock_down)
			t2 = webdl.WebDL(self.lstore, row,
					t.src_file,
					t.get_fullfile(),
					final,
					self.watch_file,
					self.print_log)
		self.lstore.remove(iter)
		#sl = self.tv.get_selection()
		#sl.select_iter(row)
		t2.start()
		#self.sync_ui()

	def print_log(self, msg, pb=None):
		if self.error_flag == 0 and pb == self.stock_error:
			self.log_toggle.show()
			self.error_flag = 1
			self.log_image.set_from_stock(self.stock_error,
							gtk.ICON_SIZE_MENU)
		row = self.logstore.append([pb, time.strftime("%r"), msg,
						time.time()])
		#adj = self.log_tv.get_parent().get_vadjustment()
		#adj.set_value(adj.upper)
		return row

	def add_row(self, pb=None):
		return self.lstore.append([pb, 'Connecting...',-1, None, "???", gtk.ICON_SIZE_DND])

	def on_check_window_shown(self, obj):
		self.setwindowvis(obj.get_active())

	def on_log_delete(self, obj, ev=None):
		self.log_window.hide()
		self.log_toggle.set_active(False)
		return True

	def on_window_delete(self, obj, ev):
		global use_tray
		if use_tray != 1:
			self.on_quit(None)
			return False
		else:
			self.setwindowvis(False)
		return True

	def on_hide_window_menu(self, obj):
		self.setwindowvis(False)

	def kill_thread(self, model, path, iter, data):
		t = model.get_value(iter, 3)
		if t is not None:
			if t.isAlive():
				t.please_exit()

	def reap_thread(self, model, path, iter, data):
		t = model.get_value(iter, 3)
		if t is not None:
			if t.isAlive():
				t.join()

	def on_stop_all_transfers(self, obj):
		self.lstore.foreach(self.kill_thread, None)
		self.sync_ui()

	def alive_count(self):
		numactive = 0
		iter = self.lstore.get_iter_first()
		while iter:
			t = self.lstore.get_value(iter, 3)
			if t:
				if t.isAlive():
					numactive += 1
			iter = self.lstore.iter_next(iter)
		return numactive

	def dead_count(self):
		num = 0
		iter = self.lstore.get_iter_first()
		while iter:
			t = self.lstore.get_value(iter, 3)
			if t:
				if not t.isAlive():
					num += 1
			else:
				num += 1
			iter = self.lstore.iter_next(iter)
		return num

	def quit_update(self):
		if self.killflag.isSet():
			self.sd.please_exit()
			gtk.gdk.threads_enter()
			if self.alive_count() == 0:
				gtk.main_quit()
				gtk.gdk.threads_leave()
				return False
			else:
				self.lstore.foreach(self.kill_thread, None)
			gtk.gdk.threads_leave()
				
		return True

	def on_quit(self, obj, ev=None):
		gobject.timeout_add(QUIT_TIMEOUT, self.quit_update)
		self.lstore.foreach(self.kill_thread, None)
		self.applet_window.hide()
		self.main_window.hide()
		self.log_window.hide()
		if not self.killflag.isSet():
			self.killflag.set()

	def sync_ui_list(self, model, path, iter, data):
		t = model.get_value(iter, 3)

		if t is None:
			return

		if isinstance(t, webdl.WebDL):
			if t.progress == 100:
				pb = t.mime_pb
			else:
				if t.killflag.isSet() or not t.isAlive():
					pb = self.stock_error
				else:
					pb = self.stock_down

		elif isinstance(t, btdl.BTDL):
			if t.isAlive():
				if t.killflag.isSet():
					if t.progress == 100:
						pb = t.mime_pb
					else:
						pb = self.stock_error
				else:
					if t.progress == 100:
						pb = self.stock_up
					else:
						pb = self.stock_downup
			else:
				if t.progress == 100:
					pb = t.mime_pb
				else:
					pb = self.stock_error

		old = self.lstore.get_value(iter, 0)
		if old == pb:
			return

		if pb == t.mime_pb:
			try:
				iinfo = self.theme.lookup_icon(pb,
							self.icon_height, 0)
				self.add_icon(pb, iinfo.get_filename())
			except:
				self.print_log("Error getting mime file for: " +
						pb, self.stock_error)
	
		self.lstore.set_value(iter, 0, pb)

	def sync_ui(self):
		self.lstore.foreach(self.sync_ui_list, None)

		if self.alive_count() > 0:
			self.stop_all_menu.set_sensitive(True)
			self.stop_all_cmenu.set_sensitive(True)
		else:
			self.stop_all_menu.set_sensitive(False)
			self.stop_all_cmenu.set_sensitive(False)

		if self.dead_count() > 0:
			self.clean_up_button.set_sensitive(True)
			self.clean_up_menu.set_sensitive(True)
		else:
			self.clean_up_button.set_sensitive(False)
			self.clean_up_menu.set_sensitive(False)

		sl = self.tv.get_selection()

		if sl.count_selected_rows() == 0:
			self.exec_button.set_sensitive(False)
			self.exec_menu.set_sensitive(False)
			self.location_button.set_sensitive(False)
			self.location_menu.set_sensitive(False)
			self.properties_menu.set_sensitive(False)
			self.properties_button.set_sensitive(False)
			self.stop_button.set_sensitive(False)
			self.stop_menu.set_sensitive(False)
			self.restart_button.set_sensitive(False)
			self.restart_menu.set_sensitive(False)
			self.remove_button.set_sensitive(False)
			self.remove_menu.set_sensitive(False)
			return

		(mod, iter) = sl.get_selected()
		t = self.lstore.get_value(iter, 3)

		self.location_button.set_sensitive(True)
		self.location_menu.set_sensitive(True)
		self.properties_menu.set_sensitive(True)
		self.properties_button.set_sensitive(True)
		self.exec_button.set_sensitive(True)
		self.exec_menu.set_sensitive(True)

		if t.isAlive():
			self.restart_button.set_sensitive(False)
			self.restart_menu.set_sensitive(False)
			self.stop_button.set_sensitive(True)
			self.stop_menu.set_sensitive(True)
			self.remove_button.set_sensitive(False)
			self.remove_menu.set_sensitive(False)
		else:
			self.restart_button.set_sensitive(True)
			self.restart_menu.set_sensitive(True)
			self.stop_button.set_sensitive(False)
			self.stop_menu.set_sensitive(False)
			self.remove_button.set_sensitive(True)
			self.remove_menu.set_sensitive(True)

	def on_treeview_unselect_all(self, obj, param=None):
		self.sync_ui()

	def on_treeview_cursor_changed(self, obj, param=None):
		self.sync_ui()

	def trayclicked(self, obj, ev):
		if ev.type != gtk.gdk.BUTTON_PRESS:
			return True

		if ev.button == 1:
			#left click, show/hide window
			if self.hidden == True :
				self.setwindowvis(False)
			elif self.hidden == False :
				self.setwindowvis(True)
		elif ev.button == 3:
			#right click, context menu
			self.tray_menu.popup(None, None, None, ev.button,
						ev.time)
		return True

	def setwindowvis(self, yes):
		if yes == True:
			self.main_window.show()
			self.tv.set_enable_search(True)
			self.tv.grab_focus()
		elif yes == False:
			self.main_window.hide()
		else :
			return
		self.hidden = yes
		self.check_shown.set_active(self.hidden)

	def traydetach(self):
		return
		
	def run(self):
		try:
			gtk.main()
		except:

			print "Exception in main"
			if not self.killflag.isSet():
				self.sd.please_exit()
				self.lstore.foreach(self.kill_thread, None)
				print "Waiting for threads to exit... "
				self.lstore.foreach(self.reap_thread, None)
				self.sd.join()
				print "Done"

if __name__ == "__main__":

	prog = gnome.program_init(	'freeloader',
					'0.3',
					gnome.libgnome_module_info_get(),
					sys.argv,
					parm_table)

	(files,args) = prog.get_popt_args()

	gc = gconf_client = gconf.client_get_default()

	fullkey = gconf_dir + "/localsock"
	sockfd = gc.get_string(fullkey)

	fullkey = gconf_dir + "/localsockkey"
	sockkey = gc.get_string(fullkey)

	failed = 0
	flags = ''
	if args['show-window'] == 1:
		flags += 's'
	elif args['hide-window'] == 1:
		flags += 'h'
	if args['stop-all'] == 1:
		flags += 'x'
	if args['quit'] == 1:
		flags += 'q'

	# default to showing window
	if len(flags) == 0 and len(files) == 0:
		flags += 's'

	try:
		#dbus.init_gthreads()
		#gtk.gdk.threads_init()

		#lbus = dbus.Bus(dbus.Bus.TYPE_SESSION, False)
		#service = lbus.get_service(dbus_service_name)
		#o = service.get_object("/Freeloader",
		#			dbus_service_name)
		#o.ping()

		try:
			shutdown_value = socket.SHUT_RDWR
		except AttributeError:
			shutdown_value = 2 # python2.3 fix

		if len(flags) > 0:
			while(len(flags) < 10):
				flags += "-"

			sock = socket.socket(socket.AF_UNIX,
						socket.SOCK_STREAM)
			sock.connect(sockfd)
			sock.sendall(sockkey)
			sock.sendall(flags)
			sock.shutdown(shutdown_value)
			sock.close()

		for f in files:
			try:
				if len(f) > 512:
					self.print_log(
						"Unusually large input file " +\
						"has been ignored for security",
						self.stock_error)
					continue
				sock = socket.socket(socket.AF_UNIX,
							socket.SOCK_STREAM)
				sock.connect(sockfd)
				sock.sendall(sockkey)
				sock.sendall("----------")
				sock.sendall(f)
               
				sock.shutdown(shutdown_value)
				sock.close()
                
			except:
				failed += 1
				raise

	except:
		gtk.gdk.threads_init()
		fl = Freeloader(files)
		fl.run()

		#lbus = dbus.Bus(dbus.Bus.TYPE_SESSION, True)
		#service = dbus.Service(dbus_service_name, lbus)
		#fl = Freeloader(service, files)
		#fl.run()

