#!/usr/bin/env python


#    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 3 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, see <http://www.gnu.org/licenses/>.


# TransmissionScreenlet (c) Vaarsuvius <vaarsuvius@gmx.net>
# INFO:
# - shows output of transmission-remote -l (sorted by progress) and provides some controls

import screenlets
from screenlets.options import ColorOption, StringOption, IntOption, FontOption, DirectoryOption, BoolOption
import pango
import gobject
import gtk
import os
import subprocess
import operator
from screenlets import DefaultMenuItem
try:
	from Xlib.display import Display
	xlibLoaded = True
except ImportError:
	xlibLoaded = False
try:
	import pynotify
	pynotifyLoaded = True
except ImportError:
	pynotifyLoaded = False


screen_width = 0
button_offset_y = 6
multiplierPtPx = 1.3333

class ProcessedTorrentsList:
	"""helper class to handle list of torrents for auto-run command and notification"""
	
	def __init__(self):
		self.__torrents = []
	
	def __add(self,torrentID,name,checked=False):
		self.__torrents.append([int(torrentID),checked,name])
		#print str(self.__torrents)
	
	def removeNotChecked(self):
		newlist = []
		for i in range(0,len(self.__torrents)):
			if self.__torrents[i][1] == True:
				newlist.append(self.__torrents[i])
		self.__torrents = newlist
		del (newlist)
		
	def uncheckAll(self):
		for i in range(0,len(self.__torrents)):
			self.__torrents[i][1] = False
			
	def checkOrAdd(self,torrentID,name):
		for i in range(0,len(self.__torrents)):
			if self.__torrents[i][0] == int(torrentID):
				self.__torrents[i][1] = True
				return False
		self.__add(torrentID,name,True)
		return True
		
	def getNameByID(self,torrentID):
		for i in range(len(self.__torrents)-1,-1,-1):
			if self.__torrents[i][0] == int(torrentID):
				return self.__torrents[i][2]
		return False
		
	def __str__(self):
		return str(self.__torrents)

class TransmissionScreenlet(screenlets.Screenlet):
	"""shows output of transmission-remote -l (sorted by progress) and provides some controls"""
	
	__name__ = 'TransmissionScreenlet'
	__version__ = '0.2beta3.test'
	__author__ = 'Vaarsuvius'
	__desc__ = 'output of transmission-remote -l and some controls'
	__timeout = None
	update_interval = 6
	__has_focus = False
	frame_color = (0.1, 0.1, 0.1, 0.7)
	text_color =  (0.75, 0.75, 0.75, 0.7)
	button_color = (0.6, 0.6, 0.6, 0.7)
	__output = ''
	font = 'Monospace 8'
	__font_name = 'Monospace'
	__font_size = 8
	__font_size_px = 10.6664
	showBtns = True
	__button_lbl_offset_y = 5
	__button_height = 21
	__button_width = 0
	__pausedTorrents=[]
	tmCMD = 'transmission-remote'
	dlDir="/var/transmission/downloads/"
	browserCMD='firefox'
	__webpage='/transmission/web/'
	filemgrCMD='thunar'
	torrentsCt=7
	daemonHost='localhost'
	daemonPort=9091
	notification = True
	runOnce = True
	autoCMD = []
	tmdStartCMD = ['sudo','/etc/init.d/transmission','start']
	tmdStopCMD = ['sudo','/etc/init.d/transmission','stop']
	daemonStarted = False


	def __init__(self, **keyword_args):
		screenlets.Screenlet.__init__(self, width=300, height=100, uses_theme=False, 
				**keyword_args)
		
		if xlibLoaded:
			display = Display()
			screen_width = display.screen().width_in_pixels
			self.width = screen_width
			del (display)
		else:
			self.width = 800

		self.processedTorrents = ProcessedTorrentsList()
		
		self.add_options_group('Options', 'Options')
		self.add_options_group('Look & Feel','Look & Feel')
		
		self.add_option(IntOption('Options', 'update_interval', 
			self.update_interval, 'Update interval in seconds', 
			'Update interval in seconds', min=1, max=1000))

		self.add_option(IntOption('Options', 'torrentsCt', self.torrentsCt, 
			'Number of torrents shown', 'Number of torrents shown on list', 
			min=1, max=1000))
		
		if not pynotifyLoaded:
			self.notification = False
			self.add_option(BoolOption('Options', 'notify', self.notification,
				'Enable Notification', 'Enable Notification via libnotify',disabled = True))
		else:
			self.add_option(BoolOption('Options', 'notify', self.notification,
				'Enable Notification', 'Enable Notification via libnotify',disabled = False))
			pynotify.init('TransmissionScreenlet')

		self.add_option(IntOption('Options', 'daemonPort', self.daemonPort, 
			'Daemon Port', 'Daemon Port', min=1, max=65535), realtime=False)

		self.add_option(StringOption('Options', 'daemonHost', self.daemonHost,
			'Host running Daemon', 'Host running Daemon'), realtime=False)
			
		self.add_option(StringOption('Options','startTMD','sudo /etc/init.d/transmission start',
			'Start transmission-daemon','Command to start transmission-daemon'))
		self.add_option(StringOption('Options','stopTMD','sudo /etc/init.d/transmission stop',
			'Stop transmission-daemon','Command to stop transmission-daemon'))

		self.add_option(StringOption('Options', 'filemgr', 'thunar',
			'Filemanager','Command to run filemanager'))

		self.add_option(StringOption('Options', 'browser', 'firefox',
			'Web Browser', 'Command to run web browser'))

		self.add_option(StringOption('Options', 'tm', 'transmission-remote',
			'transmission-remote command', 'Command to run transmission-remote'),realtime=False)
			
		self.add_option(StringOption('Options', 'runCMD', '', 'auto run command', 
			'Command that is automatically run on torrents which reached status "Done".\nYou can use %t for the torrentID, %n for name of the torrent, %d for download directory.\nOnly 1 command is supported, if you want to use several run a bash-script.'),
			realtime=False)
		self.add_option(BoolOption('Options', 'runOnce', self.runOnce, 'Run above command once only',
			'if checked, run autorun-command only on torrents which just completed (if not checked, command will be run at every update)'))

		self.add_option(DirectoryOption('Options', 'dlDir', self.dlDir,
			'Download Directory', 'Directory where transmission-daemon saves your downloads'))

		self.add_option(IntOption('Look & Feel', 'w', 
			self.width, 'Width', 'Width of screenlet', min=1, max=10000))

		self.add_option(IntOption('Look & Feel', 'h', 
			self.height, 'Height', 'Height of screenlet', min=1, max=5000))
		
		self.add_option(BoolOption('Look & Feel', 'showBtns', self.showBtns,
			'Show Buttons', 'Show Buttons'))
		
		self.add_option(FontOption('Look & Feel','f', 
			self.font, 'Font', 'Font'))

		self.add_option(ColorOption('Look & Feel','frame_color', 
			self.frame_color, 'Background colour', 
			'Background colour'))

		self.add_option(ColorOption('Look & Feel','button_color',
			self.button_color, 'Button colour',
			'Button colour'))

		self.add_option(ColorOption('Look & Feel','text_color', 
			self.text_color, 'Text colour', 
			'Text colour'))
		
		self.__timeout = gobject.timeout_add(6000, self.update)

	def __setattr__(self, name, value):
		#DirectoryOption seems to return False when not changed.... (?)
		if not ((name == 'dlDir') and (value == False)):
			screenlets.Screenlet.__setattr__(self, name, value)
			if name == "update_interval":
				if value > 0:
					self.__dict__['update_interval'] = value
					if self.__timeout:
						gobject.source_remove(self.__timeout)
					self.__timeout = gobject.timeout_add(int(value * 1000), self.update)
			elif name == 'w':
				self.width = value
			elif name == 'h':
				self.height = value
			elif name == 'f':
				self.font = value
				self.__font_name = str(value).rsplit(' ',1)[0]
				self.__font_size = int(str(value).rsplit(' ',1)[1])
				self.__font_size_px = self.__font_size*multiplierPtPx
				self.__button_height = int(2*self.__font_size_px)
				self.__button_lbl_offset_y = int((float(self.__button_height)-self.__font_size_px)/2)
				self.redraw_canvas()
				self.redraw_canvas()
			elif name == 'frame_color':
				self.redraw_canvas()
			elif name == 'button_color':
				self.redraw_canvas()
			elif name == 'text_color':
				self.redraw_canvas()
			elif name == 'showBtns':
				self.redraw_canvas()
			elif name == 'runCMD':
				self.autoCMD = self.__commandFormat(value)
			elif name == 'tm':
				self.tmCMD = ''.join(self.__commandFormat(value))
			elif name == 'browser':
				self.browserCMD = ''.join(self.__commandFormat(value))
			elif name == 'filemgr':
				self.filemgrCMD = ''.join(self.__commandFormat(value))
			elif name == 'startTMD':
				self.tmdStartCMD = self.__commandFormat(value)
			elif name == 'stopTMD':
				self.tmdStopCMD = self.__commandFormat(value)
		else:
			pass

	def on_init(self):
		self.add_default_menuitems(DefaultMenuItem.XML)
		self.add_default_menuitems()
		tmp = self.notification
		self.notification = False
		self.update()
		self.notification = tmp

	def update(self):
		command = [self.tmCMD,self.daemonHost + ':' + str(self.daemonPort),'-l']
		try:
			ret = subprocess.Popen(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate()
		except ValueError:
			del (ret)
			return True
		except OSError:
			self.__output = 'OSError: could not execute command with commandlist' + str(command)
			del (command)
			return True
		
		del (command)
				
		list = ret[0]
		origList = list
		list = list.split('\n')
		
		if (len(list) >= 2):
			list.pop(0)
			list.pop()
					
			if len(list) == 0:
				self.__output = 'TransmissionScreenlet: No torrents'
				self.redraw_canvas()
				self.daemonStarted = True
				del (ret)
				del (list)
				return True
		
			for i in range(0,len(list)):
				list[i] = list[i].split(None,8)
				if not (list[i][2] == 'Done' or list[i][2] == 'Unknown' or list[i][2].endswith('sec')):
					list[i][2] = list[i][2] + ' ' + list[i][3]
					del list[i][3]
				list[i][1] = int(list[i][1].replace('%',''))
				list[i] = tuple(list[i])
			list = sorted(list,key=operator.itemgetter(1),reverse=True)
			#sortedList = list[:]
			
			for i in range(0,min(self.torrentsCt,len(list))):
				tmp = []
				for j in list[i]:
					tmp.append(str(j))
				list[i] = tmp
				del (tmp)

				list[i][0] = list[i][0].strip()
				list[i][1] = list[i][1].strip() + '%'

				if len(list[i]) > 8:
					list[i][7] = ' '.join(list[i][7:len(list[i])])
					list[i] = list[i][0:8]
				if list[i][6] == 'Up':
					list[i][6] = list[i][6] + ' ' + list[i][7][0:6]
					list[i][7] = list[i][7][10:len(list[i][7])]

			self.__output = ''
			for i in range(0,min(self.torrentsCt,len(list))):
				self.__output = self.__output + '\n' + list[i][0].ljust(3)[:3] + list[i][1].rjust(5)[:5] + list[i][2].rjust(10)[:10] + list[i][3].rjust(7)[:7] + list[i][4].rjust(7)[:7] + list[i][5].rjust(7)[:7] + '   ' + list[i][6].ljust(14)[:14] + list[i][7]
			
			self.__output = self.__output.replace('&','&amp;').strip()
			
			self.daemonStarted = True
		else:
			if ret[1].strip() != '':
				self.__output =  ret[1].replace('&','&amp;').replace('\n','')
				if self.__output.endswith('Couldn\'t connect to server'):
					self.daemonStarted = False
		
		del (ret)
		
		self.redraw_canvas()
		self.runOnFinishedTorrents(commandList=self.autoCMD,notify=self.notification,trList = origList, runOnce = self.runOnce)
		
		del (list)
		#del (sortedList)
		del (origList)
		return True
			
	def on_draw(self, ctx):
		
		ctx.scale(self.scale, self.scale)
		
		ctx.set_source_rgba(*self.frame_color)

		self.draw_rectangle(ctx, 0, 0, self.width, self.height, fill=True)

		if self.showBtns:
			#self.get_text_width does not return correct value ... +40 as workaround ???
			self.__button_width = 40+self.get_text_width(ctx,"Open Download Directory",self.font)

			ctx.set_source_rgba(*self.button_color)
			self.draw_rectangle(ctx,self.width-(self.__button_width+7),button_offset_y,self.__button_width,self.__button_height,fill=False)
			self.draw_rectangle(ctx,self.width-(self.__button_width+7),button_offset_y+self.__button_height+3,self.__button_width,self.__button_height,fill=False)
			self.draw_rectangle(ctx,self.width-(self.__button_width+7),button_offset_y+(self.__button_height+3)*2,self.__button_width,self.__button_height,fill=False)	
			self.draw_rectangle(ctx,self.width-(self.__button_width+7),button_offset_y+(self.__button_height+3)*3,self.__button_width,self.__button_height,fill=False)

			ctx.set_source_rgba(*self.text_color)
			self.draw_text(ctx, 'Clear Finished Torrents',self.width-(self.__button_width+7),button_offset_y+self.__button_lbl_offset_y,self.__font_name,self.__font_size,self.__button_width, allignment=pango.ALIGN_CENTER)
			self.draw_text(ctx, 'Add Torrent',self.width-(self.__button_width+7),button_offset_y+self.__button_lbl_offset_y+self.__button_height+3,self.__font_name,self.__font_size,self.__button_width, allignment=pango.ALIGN_CENTER)
			self.draw_text(ctx, 'Open webUI',self.width-(self.__button_width+7),button_offset_y+self.__button_lbl_offset_y+(self.__button_height+3)*2,self.__font_name,self.__font_size,self.__button_width, allignment=pango.ALIGN_CENTER)
			self.draw_text(ctx, 'Open Download Directory',self.width-(self.__button_width+7),button_offset_y+self.__button_lbl_offset_y+(self.__button_height+3)*3,self.__font_name,self.__font_size,self.__button_width, allignment=pango.ALIGN_CENTER)

			ctx.set_source_rgba(*self.text_color)
			self.draw_text(ctx, str(self.__output), 8, 5, self.__font_name , self.__font_size, self.width-(self.__button_width+20), allignment=pango.ALIGN_LEFT)
		else:
			ctx.set_source_rgba(*self.text_color)
			self.draw_text(ctx, str(self.__output), 8, 5, self.__font_name , self.__font_size, self.width-8, allignment=pango.ALIGN_LEFT)

	
	def on_draw_shape(self, ctx):
		self.on_draw(ctx)

	def clearFinished(self):
		self.runOnFinishedTorrents(commandList=[self.tmCMD,self.daemonHost + ':' + str(self.daemonPort),'-t%t','-r'],notify=False, ignoreProcessed = True)

	def addTorrent(self):
		"""shows filechooserdialog and adds selected torrent to transmission"""

		homedir = os.path.expanduser('~') + '/'
		dialog = gtk.FileChooserDialog("Open .torrent",None, gtk.FILE_CHOOSER_ACTION_OPEN, 
			(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
		dialog.set_current_folder(homedir)
		dialog.set_default_response(gtk.RESPONSE_OK)

		filter = gtk.FileFilter()
		filter.set_name("Torrent files")
		filter.add_pattern("*.torrent")
		filter.add_pattern("*.TORRENT")
		dialog.add_filter(filter)

		filter = gtk.FileFilter()
		filter.set_name("All files")
		filter.add_pattern("*")
		dialog.add_filter(filter)

		response = dialog.run()
		if response == gtk.RESPONSE_OK:
			command = [self.tmCMD,self.daemonHost + ':' + str(self.daemonPort),'--add', dialog.get_filename()]
			try:
				subprocess.Popen(command).wait()
			except OSError:
				print 'OSError: could not execute command with commandlist' + str(command)
				del (command)
				del (homedir)
				dialog.destroy()
				del (filter)
				del (dialog)
				return False
			
			del (command)
		
		del(homedir)

		dialog.destroy()

		del(filter)
		del(dialog)


	def openWebUI(self):
		"""opens webbrowser"""

		website = "http://" + self.daemonHost + ":" + str(self.daemonPort) + self.__webpage
		try:
			subprocess.Popen([self.browserCMD,website])
		except OSError:
			print 'OSError: could not execute command with commandlist' + str([self.browserCMD,website])
			return False

	def openDLDir(self):
		"""opens filemanager"""
		try:
			subprocess.Popen([self.filemgrCMD,self.dlDir])
		except OSError:
			print 'OSError: could not execute command with commandlist' + str([self.filemgrCMD,self.dlDir])
			return False
	
	def pauseAll(self):
		"""pauses all torrents, saves active torrents to pausedTorrents"""
		command = [self.tmCMD,self.daemonHost + ':' + str(self.daemonPort),'-l']
		try:
			list = subprocess.Popen(command,stdout=subprocess.PIPE).communicate()[0]
		except ValueError:
			del (command)
			del (proc)
			return False
		except OSError:
			print 'OSError: could not execute command with commandlist' + str(command)
			del (command)
			return False

		if len(list) != 0:
			list = list.split('\n')
			list.pop(0)
			list.pop()
		
			for i in range(0,len(list)):
				list[i] = list[i].split(None,8)
				if list[i][7] == 'Downloading' or list[i][6] == 'Seeding':
					command[2] = '-t' + str(list[i][0])
					command.append('--stop')
					try:
						subprocess.Popen(command).wait()
					except OSError:
						print 'OSError: could not execute command with commandlist' + str(command)
						del (command)
						return False
					self.__pausedTorrents.append(int(list[i][0]))
		else:
			pass
		
		del (list)
		del (command)
				
	def resume(self):
		"""resumes all torrents from pausedTorrents"""
		command = [self.tmCMD,self.daemonHost + ':' + str(self.daemonPort)]
		for i in range(0,len(self.__pausedTorrents)):
			command.append('-t' + str(self.__pausedTorrents[i]))
			command.append('--start')
			try:
				subprocess.Popen(command).wait()
			except OSError:
				print 'OSError: could not execute command with commandlist' + str(command)
				del (command)
				return False
		self.__pausedTorrents=[]
		del (command)
		
	def runOnFinishedTorrents(self, commandList=[], notify=False, ignoreProcessed = False, trList = [], runOnce = True):
		"""	checks if finished torrents exist
			runs command on them or displays notification
			commandList can use: %t = finished torrent, %d = download directory, %n = torrent name
			it's only possible to run 1 command (if you want to run several, use a bash script)
		"""
		if trList == []:
			command = [self.tmCMD,self.daemonHost + ':' + str(self.daemonPort),'-l']
			try:
				list = subprocess.Popen(command,stdout=subprocess.PIPE).communicate()[0]
			except ValueError:
				del (command)
				return False
			except OSError:
				print 'OSError: could not execute command with commandlist' + str(command)
				del (command)
				return False

			del (command)
		else:
			list = trList
		
		if not ignoreProcessed:
			self.processedTorrents.uncheckAll()
		
		list = list.split('\n')
		list.pop(0)
		if len(list) >= 1:
			for i in range(0,len(list)):
				torrent = list[i].split()
				if len(torrent) >= 1:
					if (torrent[2] == "Done"):
						#print "autorun on torrent " + str(torrent[0])
						runOnceTuple = self.__runOnceInit(runOnce,torrent[0])
						if ignoreProcessed or runOnceTuple[0] == False or self.processedTorrents.checkOrAdd(torrent[0],name=self.__getTorrentName(torrent[0])):
							name = ''
							if len(commandList) == 0:
								pass
							else:
								name = self.processedTorrents.getNameByID(torrent[0])
								if name == False:
									name = self.__getTorrentName(torrentID)
								command = self.__parseCommandList(commandList[:], torrent[0], name)
								
								try:
									subprocess.Popen(command).wait()
								except OSError:
									print 'OSError: could not execute command with commandlist' + str(command)
									del (command)
									return False
								del (command)
							if notify and (operator.xor(runOnceTuple[0],runOnceTuple[1])):
								if name == '':
									name = self.processedTorrents.getNameByID(torrent[0])
									if name == False:
										name = self.__getTorrentName(torrent[0])
								p = pynotify.Notification("Torrent complete",name)
								p.set_urgency(pynotify.URGENCY_NORMAL)
								p.show()
								del (p)
						del (runOnceTuple)
		del (list)
		
		if not ignoreProcessed:
			self.processedTorrents.removeNotChecked()
		return True
			
	def startstopDaemon(self):
		while self.__toggleDaemonState() == False:
			pass
			
	def __toggleDaemonState(self):
		if self.daemonStarted == False:
			try:
				retcode = subprocess.call(self.tmdStartCMD)
			except ValueError:
				return False
			if retcode == 0:
				del (retcode)
				return True
			else:
				#print 'Could not start Daemon, returncode ' + str(retcode)
				del (retcode)
				return False
		else:
			try:
				retcode = subprocess.call(self.tmdStopCMD)
			except ValueError:
				return False
			if retcode == 0:
				del (retcode)
				return True
			else:
				#print 'Could not stop Daemon: returncode ' + str(retcode)
				del (retcode)
				return False
				
	def __runOnceInit(self,runOnce,torrentID):
		if not runOnce:
			justFinished = self.processedTorrents.checkOrAdd(torrentID,name=self.__getTorrentName(torrentID))
			return tuple([runOnce,justFinished])
		return tuple([runOnce,False])
				
	def __getTorrentName(self,torrentID):
		command = [self.tmCMD,'-t',str(torrentID),'--files']
		try:
			filesText = subprocess.Popen(command,stdout=subprocess.PIPE).communicate()[0]
		except ValueError:
			del (command)
			return ''
		except OSError:
			print 'OSError: could not execute command with commandlist' + str(command)
			del (command)
			return ''
		del (command)

		name = filesText.split("\n")[0].replace('&','&amp;')
		name = name[0:name.rindex('(')-1]
		return name

		
	def __parseCommandList(self,commandList,torrentID,name):
		for i in range(0,len(commandList)):
			commandList[i] = commandList[i].replace("%t",str(torrentID))
			commandList[i] = commandList[i].replace("%d",self.dlDir)
			commandList[i] = commandList[i].replace("%n",name).replace("&amp;","&")
		return commandList
		
	def __commandFormat(self,commandStr):
		if not commandStr.isspace():
			cmd = commandStr.strip()
			try:
				cmd = cmd[0:cmd.index(';')]
			except ValueError:
				pass
			cmd = cmd.replace('&',' ').split()
		else:
			cmd = []
		return cmd

	def on_menuitem_select (self, id):
		if id == 'clearFinished':
			self.clearFinished()
			self.update()
		if id == 'addTorrent':
			self.addTorrent()
			self.update()
		if id == 'openWebUI':
			self.openWebUI()
		if id == 'openDLDir':
			self.openDLDir()
		if id == 'pause':
			self.pauseAll()
			self.update()
		if id == 'resume':
			self.resume()
			self.update()
		if id == 'daemon':
			self.startstopDaemon()
			self.update()
	
	def on_mouse_down (self, event):
		if event.type != gtk.gdk.BUTTON_PRESS:	    
			return False
		if self.showBtns:
			if event.button == 1:
				if (event.x > self.width-(self.__button_width+7)) and (event.x < self.width-(self.__button_width+7)+self.__button_width):
					if (event.y > button_offset_y) and (event.y < button_offset_y+(self.__button_height+2)):
						self.clearFinished()
					elif (event.y > button_offset_y+(self.__button_height+2)) and (event.y < button_offset_y+(self.__button_height+2)*2):
						self.addTorrent()
					elif (event.y > button_offset_y+(self.__button_height+2)*2) and (event.y < button_offset_y+(self.__button_height+2)*3):
						self.openWebUI()
					elif (event.y > button_offset_y+(self.__button_height+2)*3) and (event.y < button_offset_y+(self.__button_height+2)*4):
						self.openDLDir()
		else:
			pass

if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(TransmissionScreenlet)
