#!/usr/bin/env python

#
#gooload_gui.py
#
#Copyright (C) 2008 Irae Hueck Costa
#   
#This file is part of Golooad.
#
#Gooload 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.
#
#Gooload 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 Gooload.  If not, see <http://www.gnu.org/licenses/>.
#
#




from twisted.internet import task, tksupport

from Tkinter import *
from tkFileDialog import askdirectory
from tkMessageBox import showerror

import os
import sys
import subprocess
from time import sleep
import threading
import random

import gooload



COPYING = """

Send me bugs, feedback, etc at irae"""+"""hc@hotmail.com


Copyright (C) 2008 Irae Hueck Costa

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

FG = '#FFA500'
BG = '#6B8E23'

#list taken from http://www.file-extensions.org/ and extended
SEARCH_TYPES = (('music', 'mp3', 
					('a21', 'a2b', 'a2m', 'a3k', 'a52', 'aa', 'aac', 'aap', 'abc', 'abk', 'abm', 'ac3',
					'acd', 'aif', 'aifc', 'aiff', 'aifr', 'amr', 'ape', 'asf', 'asf', 'au', 'aud', 'aud',
					'aup', 'bin', 'bwf', 'cda', 'dct', 'dss', 'dts', 'dvf', 'esu', 'eta', 'fla', 'flac',
					'flt', 'gsm', 'ins', 'ins', 'itl', 'its', 'jam', 'jam', 'm4a', 'm4p', 'mdi', 'mid', 'midi', 
					'mka', 'mp+', 'mp1', 'mp2', 'mp2a', 'mp3', 'mpa', 'mpc', 'mpega', 'msv', 'mus', 'mus', 'mxc2 ',
					'nwc', 'nwp', 'ogg', 'pls', 'psb', 'psm', 'psm', 'ra', 'ram', 'rel', 'rol', 's7', 'sab', 'shn',
					'sib', 'smf', 'snd', 'speex', 'spx', 'swa', 'swd', 'tta', 'vox', 'vy3', 'wav', 'wave', 'wma', 'wpk',
					'wv', 'wvc', 'zax')),

				('video', 'avi',
					('3g2', '3gp', '3gp', '3mm', 'acw', 'asx', 'avi', 'avs', 'bay', 'bik', 'bsf', 'bup', 'dat', 'divx', 'dv',
					'dvr-ms', 'ecf', 'fbr', 'flc', 'fli', 'flic', 'flv', 'gmm', 'gmt', 'hs', 'ifo', 'ivf', 'm1v', 'm2p', 'm2ts',
					'm2v', 'm4v', 'mgv', 'mkv', 'mov', 'movie', 'mp2v', 'mp4', 'mpcpl', 'mpe', 'mpeg', 'mpg', 'nvc', 'ogm', 'qt',
					'ratDVD', 'rax', 'rm', 'rmvb', 'smi', 'stk', 'str', 'tp', 'trp', 'ts', 'urc', 'vob', 'vro', 'wm', 'wmv', 'xvid')),
	
				('document', 'pdf',
					('aa', 'abw', 'acc', 'ada', 'adb', 'adc', 'aws', 'aww', 'b4u', 'cwk', 'dif', 'doc', 'docx', 'dot', 'ebh',
					'fdf', 'gp3', 'gp4', 'gp5', 'grk', 'gtp', 'insx', 'jtd', 'key', 'keynote', 'latex', 'lwp', 'msg', 'ods',
					'odt', 'ofm', 'one', 'onepkg', 'ots', 'pdb', 'pdf', 'pdg', 'pmd', 'pot', 'pps', 'ppsx', 'ppt', 'prc', 'prd',
					'prs', 'prt', 'prv', 'prv', 'pub', 'pxl', 'qpw', 'rtf', 's85', 'sdc', 'shw', 'slk', 'slk', 'sxc', 'sxw', 'tex',
					'thmx', 'tmd', 'tmv', 'tpl', 'vor', 'vsd', 'vst', 'wb2', 'wb3', 'wk1', 'wk2', 'wk3', 'wk4', 'wke', 'wki', 'wks',
					'wku', 'wpd', 'wpd', 'wpd', 'wps', 'wpt', 'wrf', 'wri', 'xls', 'xlt', 'xps', 'xsf', 'xsn', 'ywp',
					
					#manually aded
					'pdf', 'ps', 'chm')),

				('execuatble', 'exe',
					('exe', 'dmg', 'bin')),
				
				('iso', 'iso', 
					('000', '001', '2img', '2mg', 'a4w', 'aa', 'ashdisk', 'ashprj', 'axp ', 'b5i', 'b5t', 'b6i',
					'bin', 'bwi', 'bwt', 'c2d', 'ccd', 'cd', 'cdi', 'cif', 'daa', 'dao', 'dmg', 'do', 'dsk', 'dsk',
					'dxp', 'ego', 'fcd', 'flp', 'gcd', 'gi', 'hdv', 'i00', 'i01', 'i02', 'i03', 'i04', 'i05', 'ibp',
					'ibq', 'ifu', 'ifz', 'ima', 'img', 'img', 'imz', 'iso', 'isz', 'lcd', 'md1', 'mdf', 'mds', 'ncd',
					'nib', 'nrg', 'nrv', 'nti', 'p01', 'pdi', 'po', 'pxi', 'tao', 'uif', 'vc4', 'vcd', 'vcd', 'vdi', 'vhd',
					'vmdk', 'xa', 'xgs', 'xmf')))



def debug(stri):
	print stri

def get_default_download_dir():
	return os.path.expanduser('~')


##why this dont work?
#def mainloop_do(cb):
#	#print "meinloop_do", cb
#	task.Clock().callLater(0.1, cb)

class Queue(list):
	def __init__(self):
		list.__init__(self)
	def update(self):
		while True:
			if not self:
				break
			self.pop(0)()
jobs = Queue()
mainloop_do = jobs.append





def get_home_dir():
	return os.path.expanduser('~')


def style(widget):
	print repr(widget)
	for key, val in dict(bg=BG, fg=FG, relief=FLAT, font='system').iteritems():
		
		#exceptions
		if key == "relief" and isinstance(widget, Menu):
			continue
		if isinstance(widget, Entry) and key == 'bg':
			key = FG
		if isinstance(widget, Entry) and key == 'fg':
			key = BG
		
		
		try:
			widget.config(**{key:val})
		except TclError, exc:
			pass

	children = widget.children.values()
	for child in children:
		style(child)

class Scrolling(object):
	"""argument must be a tkinter widget
	returns the given widget with a scrollbar on the right side"""
	def __new__(self, Widget, **kw):
		class Tmp(Widget):
			def __init__(self, root, **kw):
				frm = Frame(root)
				Widget.__init__(self, frm, **kw)
			
				scrollbar = Scrollbar(frm)
				self.config(yscrollcommand=scrollbar.set)
				scrollbar.config(command=self.yview)
				self.pack(side=LEFT, expand=YES, fill=BOTH)
				scrollbar.pack(side=RIGHT, fill=Y)
			
				self.pack = frm.pack
				self.grid = frm.grid
		return Tmp


class FoundFiles(Listbox):
	def __init__(self, master, download_cb, **tkkwargs):
		Listbox.__init__(self, master, **tkkwargs)
		self.bind("<Double-Button-1>", self._on_download)
		self.bind("<Button-3>", self._on_download)
		self.file_locations = []
		self.download_cb = download_cb
		
	def append(self, file_location, filename, file_size):
		self.insert(END, str(file_size).rjust(7) + " MB | " + filename)
		self.file_locations.append(file_location)
	
	def reset(self):
		self.file_locations = []
	
	def _on_download(self, dummy):
		selected = (int(i) for i in self.curselection())
		for index in selected:
			filename = self.get(index).split(" | ")[1]
			location = self.file_locations[index]
			print "downloading", filename, "at", location
			self.download_cb(filename, location)

class DownloadingFiles(Listbox):
	def __init__(self, master, **tkkwargs):
		Listbox.__init__(self, master, **tkkwargs)
		self.bind("<Double-Button-1>", self._play)
		self.files = []
		
	def append(self, file_dir, filename):
		
		#so we can know were the file is in order to play it
		self.files.append(os.path.join(file_dir, filename))
			
		self.insert(END, "waiting | " + filename)
		self.see(END)

	def chg_status(self, index, new_status):
		print index, new_status
		self.insert(index-1, str(new_status) + ' | ' + self.get(index-1).split(' | ', 1)[1])
		self.delete(index)
	
	def _play(self, dummy):
		for index in (int(i) for i in self.curselection()):
			print "playing:", self.files[index]
			self._open_file(self.files[index])
	
	if os.name == 'nt' or os.name == 'dos':
		def _open_file(self, file):
			os.startfile(file)
	elif os.name == 'posix':
		def _open_file(self, file):
			#cmd injection via bad filename may be possible?
			#and what if the user is not using gnome? i need sugestions!
			subprocess.Popen(['gnome-open', file])
	else:
		def _open_file(self, file):
			print 'sorry, i dont know how to run a file on your platform'


class GUI(Frame):
	def __init__(self, master):
		Frame.__init__(self, master)
	
		#fixme: master must be Tk object
		self.root = master
		
		self.download_dir = get_default_download_dir()
		self.is_searching = True
		
		
		# the search menu {
		search_menu = Frame(self)
		
		self.search_progress_var = IntVar()
		f = Frame(search_menu); f.pack()
		Label(f, text="active connections: ").pack(side=LEFT)
		Label(f, textvariable=self.search_progress_var).pack(side=LEFT)
		
		self.searching_frm = Frame(search_menu)
		Button(self.searching_frm, text="new search", command=self._abort_search).pack()
	
		self.not_searching_frm = Frame(search_menu)
		self.keyword_entr = Entry(self.not_searching_frm)
		self.keyword_entr.pack(side=LEFT)
		Button(self.not_searching_frm, text="search", command=self._search).pack(side=LEFT, padx=10)
		self.not_searching_frm.pack()
	
		search_menu.pack(pady=20)
		#}
	
		
		#the found files and downloading files area{
		
		downloading_files = Scrolling(DownloadingFiles)(self)
		downloading_files.config(height=1)

		#somewhat lispy
		self.found_files = Scrolling(FoundFiles)(self,
			download_cb=lambda filename, location:
					downloading_files.append(self.download_dir, filename) or \
					gooload.download(from_=location,
						to=self.download_dir,
						as_=filename,
						progress_cb=lambda progress, index=downloading_files.size():
									mainloop_do(lambda: #tkinter is not threadsafe therefore only the mainloop may chg the GUI
												downloading_files.chg_status(index, progress))))
		self.found_files.pack(expand=True, fill=BOTH)
		downloading_files.pack(expand=True, fill=BOTH)
		#}
		
		self._mk_menubar()
	
	def update_search_progress(self):
		self.search_progress_var.set(gooload.conns_manager.num_conns)
	
	def _mk_menubar(self):
		menubar = Menu(self)
		
		file_menu = Menu()
		menubar.add_cascade(label="File", menu=file_menu)
		file_menu.add_command(label="Download files to", command=self._ask_for_download_dir)
		file_menu.add_command(label="New window", command=self._new_window)
		
		search_for_menu = Menu(menubar)
		menubar.add_cascade(label="Search for", menu=search_for_menu)
		self.search_type_var = StringVar()
		self.search_type_var.set('music')
		for i in SEARCH_TYPES:
			name = i[0]
			search_for_menu.add_radiobutton(label=name, variable=self.search_type_var, value=name)

		search_engine_menu = Menu(menubar)
		menubar.add_cascade(label="Search engine", menu=search_engine_menu)
		self.search_engine_var = StringVar()
		self.search_engine_var.set(random.choice(('yahoo', 'msn')))
		#self.search_engine_var.set("msn")
		for text, value in (
						('Yahoo', 'yahoo'),
						('MSN live search', 'msn'),
						("Google (not compatible with it's AGB)", 'google'),
					):
			search_engine_menu.add_radiobutton(label=text, variable=self.search_engine_var, value=value)
		
		menubar.add_command(label='About', command=self._about)
		
		self.root.config(menu=menubar)
	
	def _about(self):
		top = Toplevel(self)
		Label(top, text=COPYING).pack()
		style(top)
	
	def _ask_for_download_dir(self):
		dir = askdirectory()
		if not dir:
			return
		if not os.path.exists(dir):
			showerror('error', 'i am sorry,\nthe path "' + dir + '" was not found.\n try until StackError')
			self._ask_for_download_dir()
		self.download_dir = dir
	
	def _new_window(self):
		#what if python not in PATH?
		subprocess.Popen([sys.executable, __file__])
	
	def _search(self):
		self.is_searching = True
		self.not_searching_frm.pack_forget()
		self.searching_frm.pack()
		
		keyword = self.keyword_entr.get().strip()
		gooload.set_search_engine(self.search_engine_var.get())
		search_type_val = self.search_type_var.get()
		
		ok = False
		for i in SEARCH_TYPES:
			if i[0] == search_type_val:
				gooload.search_for_files(keyword=keyword,
							filetype_search = i[1],
							filetypes_accept = i[2],
							file_cb=self.found_files.append)
				ok = True
		assert ok
	
	def _abort_search(self):
		self.is_searching = False
		self.searching_frm.pack_forget()
		self.not_searching_frm.pack()
		
		gooload.conns_manager.close_all()
		
		self.found_files.delete(0, END)
		self.found_files.reset()
		
		#==============FIXED!============
		##twisted just dont understand that we want to remove ALL fucking connections!
		##therefore we need this workaround
		#def remove_all_connections():
		#	while not self.is_searching:
		#		len_removed = len([conn.loseConnection() for conn in gooload.reactor.removeAll()])
		#		if len_removed:
		#			print "removed", len_removed, "connections"
		#		mainloop_do(lambda: self.found_files.delete(0, END))
		#		sleep(0.1)
		#threading.Thread(target=remove_all_connections).start()
		



def quit(root):
	gooload.reactor.stop()
	while True:
		print "manually updating!"
		root.update()

def main():
	root = Tk()
	root.title('gooload 0.8 BETA')
	root.wm_geometry('%sx%s+%s+%s'% (int(root.winfo_screenwidth()*0.4),
								int(root.winfo_screenheight()*0.8),
								root.winfo_rootx(), root.winfo_rooty())) # resize
	#root.protocol("WM_DELETE_WINDOW", lambda: tksupport.uninstall() or root.destroy() or gooload.reactor.crash())
	root.protocol("WM_DELETE_WINDOW", lambda: root.destroy() or gooload.reactor.stop())

	gui = GUI(root)
	gui.pack(expand=True, fill=BOTH)
	
	style(root)
	
	tksupport.install(root)
	task.LoopingCall(jobs.update).start(0.01, False)
	task.LoopingCall(gui.update_search_progress).start(0.1, False)
	gooload.reactor.run()


if __name__ == "__main__":
	main()







