#!/usr/bin/python
# twisted and deluge are currently not available for python3
#
# This is a part of the external applets for Cairo-Dock
# Copyright : (C) 2011 by Fabounet
# E-mail : fabounet@glx-dock.org
#
# 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.
# http://www.gnu.org/licenses/licenses.html#GPL

####################
### dependancies ###
####################

from __future__ import print_function

try:
	# Merge GLib's main loop with Twister's one
	from twisted.internet import glib2reactor
	glib2reactor.install()
	# Import the client module
	from deluge.ui.client import client
	# Import the reactor module from Twisted - this is for our mainloop
	from twisted.internet import reactor
	# Set up the logger to print out errors
	from deluge.log import setupLogger
	import deluge.component
	setupLogger()
except ImportError as e:
	print(e)
	print("This applet is designed to be used with Deluge 1.2 or later, make sure it is installed.")
	exit()
import subprocess
from os import popen, kill
from math import sqrt

try:
	from gobject import timeout_add
except:
	from gi.repository.GObject import timeout_add

from CDApplet import CDApplet, _

# Quick-info enum
INFO_NONE           = 0
INFO_DL_SPEED       = 1
INFO_PERCENT        = 2
INFO_PERCENT_NUMBER = 3

def format_bytes(x):
	unit=''
	if x < 1024:
		unit = 'B'
		y = x
	elif x < 1048576:
		unit = 'K'
		y = float(x) / 1024
	elif x < 1073741824:
		unit = 'M'
		y = float(x) / 1048576
	else:
		unit = 'G'
		y = float(x) / 1073741824
	return y,unit
	
html_escape_table = {
	"&": "&amp;",
	'"': "&quot;",
	"'": "&apos;",
	">": "&gt;",
	"<": "&lt;",
	}

def html_escape(text):
	"""Produce entities within text."""
	return "".join(html_escape_table.get(c,c) for c in text)

####################
### Applet class ###
####################
class Applet(CDApplet):
	def __init__(self):
		# define internal variables
		self.d = None
		self.cClass = 'deluge'
		self.bHasFocus = False
		self.iSidGetData = 0
		self.iSidTryConnect = 0
		
		self.bConnected = False
		self.bExit = False
		
		# call high-level init
		CDApplet.__init__(self)
	
	##### private methods #####
	
	# Connection
	
	def try_connect(self):
		#print "try_connect..."
		if self.bConnected:
			self.iSidTryConnect = 0
			return False
		else:
			self.connnect_to_daemon()
			return True
	
	def connnect_to_daemon(self):
		self.d = client.connect(self.config['server'], self.config['port'], self.config['user'], self.config['password'])
		self.d.addCallback(self.on_connect_success)
		self.d.addErrback(self.on_connect_fail)
	
	def on_connect_fail(self,reason):
		#print "Connection failed!"
		#print "reason:", reason
		self.bConnected = False
		self.icon.SetQuickInfo("")
		
		if self.iSidTryConnect == 0:
			self.iSidTryConnect = timeout_add(2000,self.try_connect)
	
	def on_connect_success(self,result):
		print("*** connected to Deluge!")
		self.bConnected = True
		if self.config['progressbar']:  # add a progress bar if required
			self.icon.AddDataRenderer('progressbar', 1, '')
		if self.iSidGetData == 0:
			self.iSidGetData = timeout_add (2000, self.update_data)
	
	def daemon_disconnect_callback(self):
		print("*** Disconnected from deluge daemon")
		self.bConnected = False
		if not self.bExit:  # we didn't get disconnected because the applet was stopped -> try to reconnect
			self.icon.SetQuickInfo("")
			if self.config['progressbar']:
				self.icon.AddDataRenderer('', 0, '')
			if self.iSidTryConnect == 0:
				self.iSidTryConnect = timeout_add(2000,self.try_connect)
	
	# Global data
	
	def update_data(self):
		if not self.bConnected:
			self.iSidGetData = 0
			return False
		
		if self.config['quick-info'] == INFO_DL_SPEED:  # display download speed
			client.core.get_session_status(["payload_download_rate"]).addCallback(self.on_got_dl_rate,'payload_download_rate')
		
		if self.config['quick-info'] == INFO_PERCENT or self.config['quick-info'] == INFO_PERCENT_NUMBER or self.config['progressbar']:  # display percent or percent+number of active torrents or progress as a progressbar
			client.core.get_torrents_status({},['progress','paused']).addCallback(self.on_got_progress)
		
		return True
	
	def on_got_dl_rate(self,value,key):
		rate=value[key]
		unit=''
		if rate < 100:
			rate = 0
		rate,unit = format_bytes(rate)
		
		form = ''
		if rate == 0:
			form = ".0f"
		elif rate < 10:
			form = ".2f"
		elif rate < 100:
			form = ".1f"
		else:
			form = ".0f"
			
		self.icon.SetQuickInfo(format(rate,form)+unit)
	
	def on_got_progress(self,status):
		n = 0
		percent = 0.
		for id, value in list(status.items()):
			if not value['paused']:
				percent = percent + value['progress']
				n = n + 1
		if n != 0:
			percent = percent / n / 100.
			if self.config['progressbar']:
				self.icon.RenderValues([percent])
			if self.config['quick-info'] == INFO_PERCENT:
				self.icon.SetQuickInfo(format(percent*100,".0f")+'%')
			elif self.config['quick-info'] == INFO_PERCENT_NUMBER:
				self.icon.SetQuickInfo(format(percent*100,".0f")+'% ('+format(n,"d")+')')
		else:  # no active torrent
			if self.config['progressbar']:
				self.icon.RenderValues([0])
			if self.config['quick-info'] == INFO_PERCENT or self.config['quick-info'] == INFO_PERCENT_NUMBER:
				self.icon.SetQuickInfo('')

	
	# Torrents info
	
	def show_torrents_info(self):
		if self.bConnected:
			client.core.get_torrents_status({},['name','progress','eta','paused','ratio','num_peers','num_seeds']).addCallback(self.on_got_torrents_status)
		else:
			self.icon.ShowDialog(_("Deluge is not running, or is not responding to us."), 4)
	
	def on_got_torrents_status(self,status):
		info = ""
		n = 0
		for id, value in list(status.items()):
			info += "<b>"+html_escape(value['name'])+"</b>:\n"
			info += "  " + _("Progress:") + " <b>"+format(value['progress'],".1f")+'%</b>'
			if value['paused']:
				info += " <i>(" + _("paused") + ")</i>\n"
			else:  # active torrent
				t = value['eta']
				if t > 0:
					info += " <i>(" + _("Time remaining:") + " "
					d=h=m=s=0
					if t > 86400:
						d = int(t / 86400) # at least 1 day
						h = int((t - d*86400) / 3600)
						info += str(d) + " " + _("days") + " "
						if h > 0:
							info += str(h) + _("h")
					else:
						h = int(t / 3600)
						m = int((t - h*3600) / 60)
						s = int(t - h*3600 - m*60)
						if h > 0:
							info += str(h) + _("h") + " "
						if h > 0 or m > 0:
							info += str(m) + _("min") + " "
						info += str(s) + _("sec")
					info += ")</i>\n"
				elif t < 0:
					info += " <i>(" + _("Time remaining: Unknown") + ")</i>\n"
				else:
					info += " <i>(" + _("Finished") + ")</i>\n"
				info += "  " + _("Number of peers:") + " "+str(value['num_peers'])+", " + _("and seeds:") + " "+str(value['num_seeds'])+"\n"
			
			info += "  " + _("Ratio:") + " "+format(value['ratio'],".2f")+'\n'
			n += 1
		if n == 0:
			info += "<i>" + _("No torrent in the list") + "</i>\n"
		
		client.core.get_session_status(["total_payload_download","total_payload_upload"]).addCallback(self.on_got_total_amount,info)
	
	def on_got_total_amount(self,values,info):
		info += "\n<b>" + _("Total amount of data:") + "</b>\n"
		
		dl = values["total_payload_download"]
		if dl < 100:
			form = ".0f"
		else:
			form = ".1f"
		dl,unit = format_bytes(dl)
		info += " - " + _("Received:") + " "+format(dl,form)+unit+"\n"
		
		ul = values["total_payload_upload"]
		if ul < 100:
			form = ".0f"
		else:
			form = ".1f"
		ul,unit = format_bytes(ul)
		info += " - " + _("Sent:") + " "+format(ul,form)+unit
		
		dialog_attributes = {
			"icon" : "deluge",
			"message" : info,
			"use-markup" : True,
			"time-length" : 4+len(info)/40 }
		widget_attributes = {}
		self.icon.PopupDialog (dialog_attributes, widget_attributes)
	
	##### applet definition #####
	
	def get_config(self,keyfile):
		self.config['shortkey'] 	= keyfile.get('Configuration', 'shortkey')
		self.config['quick-info'] 	= keyfile.getint('Configuration', 'quick-info')
		self.config['progressbar'] 	= keyfile.getboolean('Configuration', 'progressbar')
		
		self.config['server'] 		= keyfile.get('Configuration', 'server')
		self.config['port'] 		= keyfile.getint('Configuration', 'port')
		self.config['user'] 		= keyfile.get('Configuration', 'user')
		self.config['password'] 	= keyfile.get('Configuration', 'password')
		if self.config['server'] == '':
			self.config['server'] = '127.0.0.1'
		if self.config['port'] == 0:
			self.config['port'] = 58846
		
	def end(self):
		print("*** end of Deluge applet")
		self.bExit = True  # to not try to reconnect when we get the 'disconnected' signal
		reactor.stop()
		client.disconnect()
	
	def begin(self):
		self.icon.BindShortkey([self.config['shortkey']])
		self.icon.ControlAppli(self.cClass)
		
		client.set_disconnect_callback(self.daemon_disconnect_callback)
		self.connnect_to_daemon()
		reactor.run()
	
	def reload(self):
		self.icon.BindShortkey([self.config['shortkey']])
		# set or remove the progress bar
		if self.config['progressbar']:
			self.icon.AddDataRenderer('progressbar', 1, '')
		else:
			self.icon.AddDataRenderer('', 0, '')
		if self.config['quick-info'] == INFO_NONE:
			self.icon.SetQuickInfo('')
		
	##### callbacks #####
	
	def on_click(self,iState):
		Xid = self.icon.Get("Xid")
		if Xid != 0:
			if self.bHasFocus:
				self.icon.ActOnAppli("minimize")
			else:
				self.icon.ActOnAppli("show")
		else:  # Deluge not started, or in the systray.
			print("launch Deluge...")
			if not self.bConnected:
				try:
					subprocess.Popen('deluged')
				except OSError as e:
					print("You need to install 'deluged'")  # no need to make it a dialog, since deluged is also required by the client.
			subprocess.Popen(self.cClass)
	
	def on_middle_click(self):
		self.show_torrents_info()
	
	def on_build_menu(self):
		if self.bConnected:
			items = [ {
					"label": _("Pause all torrents"),
					"icon" : "gtk-media-pause",
					"menu" : CDApplet.MAIN_MENU_ID,
					"id"   : 1
				}, {
					"label": _("Resume all torrents"),
					"icon" : "gtk-media-play",
					"menu" : CDApplet.MAIN_MENU_ID,
					"id"   : 2
				}, {
					"label": _("Torrents info") + " (" + _("middle-click") + ")",
					"icon" : "gtk-info",
					"menu" : CDApplet.MAIN_MENU_ID,
					"id"   : 3
				}, {
					"label": _("Quit"),
					"icon" : "gtk-quit",
					"menu" : CDApplet.MAIN_MENU_ID,
					"id"   : 4
				} ]
			self.icon.AddMenuItems(items)
		
	def on_menu_select(self,iNumEntry):
		if iNumEntry == 1:
			client.core.pause_all_torrents()
		elif iNumEntry == 2:
			client.core.resume_all_torrents()
		elif iNumEntry == 3:
			self.show_torrents_info()
		elif iNumEntry == 4:
			client.daemon.shutdown()  # closes the daemon but not the client
			p = subprocess.Popen(['ps', '-C', 'deluge', '--no-headers'], stdout=subprocess.PIPE)
			out, err = p.communicate()
			for line in out.splitlines():
				pid = int(line.split(None, 1)[0])
				kill(pid, 15)  # SIGTERM
		
	def on_drop_data(self,cReceivedData):
		print("*** received: "+cReceivedData)
		if self.bConnected:
			client.core.add_torrent_url(str(cReceivedData),None)
		else:
			subprocess.Popen('deluged')
			popen(self.cClass+" "+cReceivedData+"&")
	
	def on_shortkey(self,key):
		self.show_torrents_info()
	
	def on_change_focus(self,has_focus):
		self.bHasFocus = has_focus
	
############
### main ###
############
if __name__ == '__main__':
	Applet().run()
