#! /usr/bin/python
# -*- coding=utf-8 -*-

"""
    This file is part of Torrent Search.
    
    Torrent Search 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.

    Torrent Search 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/>.
"""

import gtk, gobject, thread, os, libxml2, imp, httplib2, time, urllib
from exceptions import *
from informations import *
from constants import *
from lang import _

class PluginsUpdatesChecker(gtk.Dialog):
   def __init__(self,app):
      self._app=app
      self.plugins_list_lock=thread.allocate_lock()
      self.status_lock=thread.allocate_lock()
      self.submesg_lock=thread.allocate_lock()
      self.progress_lock=thread.allocate_lock()
      self.submesg=""
      self.progress=None
      self._status=0
      gtk.Dialog.__init__(self,_("CHECKING_PLUGIN_UPDATES"),app)
      self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
      self.set_deletable(False)
      self.child.set_border_width(0)
      self.connect('key_press_event',self.on_key_press_event)
      vbox=gtk.VBox()
      self.child.add(vbox)
      vbox.set_border_width(5)
      vbox.set_spacing(10)
      self.main_mesg=gtk.Label()
      self.main_mesg.set_width_chars(50)
      self.main_mesg.set_alignment(0,0.5)
      vbox.pack_start(self.main_mesg)
      self.pb=gtk.ProgressBar()
      vbox.pack_start(self.pb)
      self.sub_mesg=gtk.Label()
      self.sub_mesg.set_alignment(0,0.5)
      vbox.pack_start(self.sub_mesg)
      self.cancel_button=gtk.Button(stock=gtk.STOCK_CANCEL)
      self.cancel_button.connect('clicked',lambda w:self.cancel())
      self.action_area.pack_start(self.cancel_button)
   def cancel(self):
      self.status=-2
      self.response(gtk.RESPONSE_CANCEL)
   def _get_submesg(self):
      self.submesg_lock.acquire()
      res=self._submesg
      self.submesg_lock.release()
      return res
   def _set_submesg(self,value):
      self.submesg_lock.acquire()
      self._submesg=value
      self.submesg_lock.release()
   submesg=property(_get_submesg,_set_submesg)
   def _get_progress(self):
      self.progress_lock.acquire()
      res=self._progress
      self.progress_lock.release()
      return res
   def _set_progress(self,value):
      self.progress_lock.acquire()
      self._progress=value
      self.progress_lock.release()
   progress=property(_get_progress,_set_progress)
   def _get_status(self):
      self.status_lock.acquire()
      res=self._status
      self.status_lock.release()
      return res
   def _set_status(self,value):
      self.status_lock.acquire()
      if self._status>=0:
         self._status=value
      self.status_lock.release()
   status=property(_get_status,_set_status)
   def _get_plugins_list(self):
      self.plugins_list_lock.acquire()
      res=self._plugins_list
      self.plugins_list_lock.release()
      return res
   def _set_plugins_list(self,value):
      self.plugins_list_lock.acquire()
      self._plugins_list=value
      self.plugins_list_lock.release()
   plugins_list=property(_get_plugins_list,_set_plugins_list)
   def on_key_press_event(self,widget,event):
      if event.keyval==65307:
         return True
   def check_status(self):
      if self.progress==None:
         self.pb.pulse()
      else:
         self.pb.set_fraction(self.progress)
      if self.status==1:
         self.set_main_mesg(_("CHECKING_PLUGINS_VERSIONS"))
      if self.status==2:
         self.set_main_mesg(_("DOWNLOADING_PLUGINS_UPDATES"))
         self.set_sub_mesg(self.submesg)
      if self.status==3:
         self.pb.set_fraction(1)
         self.set_main_mesg(_("DONE"))
         self.set_sub_mesg("")
         self.cancel_button.set_sensitive(False)
         gobject.timeout_add_seconds(1,self.response,gtk.RESPONSE_CLOSE)
         return False
      if self.status==4:
         self.pb.set_fraction(1)
         self.set_main_mesg(_("DONE"))
         self.set_sub_mesg("")
         self.response(gtk.RESPONSE_CLOSE)
         return False
      if self.status==-1:
         self.pb.set_fraction(1)
         self.set_main_mesg(_("FAILED"))
         self.set_sub_mesg("")
         self.cancel_button.set_sensitive(False)
         gobject.timeout_add_seconds(1,self.response,gtk.RESPONSE_CLOSE)
         return False
      if self.status==-2:
         return False
      return True
   def set_main_mesg(self,mesg):
      self.main_mesg.set_markup("<span size='large'><b>%s</b></span>"%mesg)
   def set_sub_mesg(self,mesg):
      self.sub_mesg.set_text(mesg)
   def check_versions(self):
      to_download=[]
      for i in self.plugins_list:
         must_download=True
         itime=time.strptime(i["released_time"],"%Y-%m-%d %H:%M:%S")
         for j in self._app.search_plugins:
            if j.ID==i["id"]:
               jtime=time.strptime(j.RELEASED_TIME,"%Y-%m-%d %H:%M:%S")
               if jtime>=itime:
                  must_download=False
         if must_download:
            to_download.append(i)
      if to_download:
         self.status=2
         self.download_updates(to_download)
      else:
         self.status=3
   def download_updates(self,to_download):
      c=httplib2.Http()
      n=len(to_download)
      p=0.
      downloaded=0
      for i in to_download:
         if self.status<=0:
            break
         try:
            self.submesg=i["title"]
            url=i["download_url"]
            resp,content=c.request(url)
            if resp.status==200:
               path=os.path.join(APPDATA_PATH,"search-plugins",i["id"])
               if not os.path.exists(path):
                  self._app.rec_mkdir(path)
               metafile=os.path.join(path,"metadata.xml")
               codefile=os.path.join(path,i["filename"])
               f=open(codefile,"w")
               f.write(content)
               f.close()
               tree=libxml2.newDoc("1.0")
               root=libxml2.newNode("plugin")
               tree.setRootElement(root)
               root.setProp("id",i["id"])
               root.setProp("version",i["version"])
               for j in ["title","released_time","author","filename","classname","download_url","website_url","icon_url","require_auth"]:
                  if j in i:
                     root.newTextChild(None,j,i[j])
               tree.saveFormatFileEnc(metafile,"utf-8",True)
               downloaded+=1
         except:
            pass
         p+=1
         self.progress=p/n
      if downloaded:
         self.status=4
      else:
         self.status=3
   def parse_plugin(self,node):
      res={}
      res["id"]=node.prop('id')
      res["version"]=node.prop('version')
      child=node.children
      while child:
         if child.type=="element":
            res[child.name]=child.getContent()
         child=child.next
      return res
   def parse_list(self,tree):
      root=tree.getRootElement()
      res=[]
      child=root.children
      while child:
         if child.name=="plugin":
            res.append(self.parse_plugin(child))
         child=child.next
      return res
   def retrieve_list(self,threaded=False):
      if not threaded:
         thread.start_new_thread(self.retrieve_list,(True,))
         return
      c=httplib2.Http()
      resp,content=c.request("http://torrent-search.sourceforge.net/plugins-db/"+VERSION)
      if resp.status==200:
         tree=libxml2.parseDoc(content)
         self.plugins_list=self.parse_list(tree)
         self.status=1
         if self.status>=0:
            self.check_versions()
      else:
         self.status=-1
      try:
         c=httplib2.Http()
         resp,content=c.request("http://torrent-search.sourceforge.net/plugins-db/"+VERSION)
         if resp.status==200:
            tree=libxml2.parseDoc(content)
            self.plugins_list=self.parse_list(tree)
            self.status=1
            if self.status>=0:
               self.check_versions()
         else:
            self.status=-1
      except:
         self.status=-1
   def run(self):
      self.status=0
      self.show_all()
      self.pulse_timer=gobject.timeout_add(50,self.check_status)
      self.set_main_mesg(_("GETTING_PLUGINS_LIST"))
      self.retrieve_list()
      gtk.Dialog.run(self)
      self.destroy()
      gobject.source_remove(self.pulse_timer)
      return self.status==4

class PluginResult(object):
   #TODO: Add categories to results
   #TODO: Add pictures when available
   def __init__(self,label,date,size,seeders=-1,leechers=-1,magnet_link=None,hashvalue=None):
      self.size=size
      self.label=label
      self.date=date
      self.seeders=seeders
      self.leechers=leechers
      if magnet_link:
         self.magnet_link=magnet_link.lower()
         if "&" in self.magnet_link:
            i=self.magnet_link.index("&")
            self.magnet_link=self.magnet_link[:i]
      elif hashvalue:
         self.magnet_link="magnet:?xt=urn:btih:"+hashvalue.lower()
      else:
         self.magnet_link=None
   def _get_link(self):
      if not hasattr(self,"_link"):
         self._link=self._do_get_link()
      return self._link
   link=property(_get_link)
   def _get_icon(self):
      return self.plugin.icon
   icon=property(_get_icon)

class ResultsList(object):
   def __init__(self):
      self._list=[]
      self._lock=thread.allocate_lock()
   def append(self,item):
      self._lock.acquire()
      self._list.append(item)
      self._lock.release()
   def __getitem__(self,index):
      self._lock.acquire()
      res=self._list[index]
      self._lock.release()
      return res
   def __setitem__(self,index,value):
      self._lock.acquire()
      self._list[index]=value
      self._lock.release()
   def __delitem__(self,index):
      self._lock.acquire()
      del self._list[index]
      self._lock.release()
   def __iter__(self):
      self._lock.acquire()
      res=iter(self._list)
      self._lock.release()
      return res
   def __len__(self):
      self._lock.acquire()
      res=len(self._list)
      self._lock.release()
      return res

class Plugin(object):
   TITLE="No title"
   AUTHOR=""
   RELEASED_TIME=""
   def __init__(self,app):
      self._app=app
      self.search_finished_lock=thread.allocate_lock()
      self.stop_search_lock=thread.allocate_lock()
      self.results_count_lock=thread.allocate_lock()
      self.icon_lock=thread.allocate_lock()
      self.credentials_lock=thread.allocate_lock()
      self.login_cookie_lock=thread.allocate_lock()
      self.login_status_lock=thread.allocate_lock()
      self._login_cookie=None
      self._credentials=None
      self._search_finished=True
      self._stop_search=False
      self._results_count=-1
      self._icon_url=None
   def _get_login_cookie(self):
      self.login_cookie_lock.acquire()
      res=self._login_cookie
      self.login_cookie_lock.release()
      return res
   def _set_login_cookie(self,value):
      self.login_cookie_lock.acquire()
      self._login_cookie=value
      self.login_cookie_lock.release()
   login_cookie=property(_get_login_cookie,_set_login_cookie)
   def _get_credentials(self):
      self.credentials_lock.acquire()
      res=self._credentials
      self.credentials_lock.release()
      return res
   def _set_credentials(self,value):
      self.credentials_lock.acquire()
      self._credentials=value
      self.credentials_lock.release()
   credentials=property(_get_credentials,_set_credentials)
   def _set_icon_url(self,url):
      if url:
         thread.start_new_thread(self._try_load_icon,(url,))
         gobject.timeout_add(100,self._watch_load_icon)
   icon_url=property(None,_set_icon_url)
   def _watch_load_icon(self):
      self.icon_lock.acquire()
      res=not hasattr(self,"_icon")
      self.icon_lock.release()
      if not res:
         self._app.notify_plugin_icon(self)
      return res
   def _try_load_icon(self,url):
      try:
         filename,msg=urllib.urlretrieve(url)
         self.icon=gtk.gdk.pixbuf_new_from_file_at_size(filename,16,16)
         os.unlink(filename)
      except:
         self.icon=None
   def _get_icon(self):
      self.icon_lock.acquire()
      if hasattr(self,'_icon'):
         res=self._icon
      else:
         res=None
      self.icon_lock.release()
      return res
   def _set_icon(self,value):
      self.icon_lock.acquire()
      self._icon=value
      self.icon_lock.release()
   icon=property(_get_icon,_set_icon)
   def _get_enabled(self):
      return not self.ID in self._app.config["disabled_plugins"]
   def _set_enabled(self,value):
      l=self._app.config["disabled_plugins"]
      if value:
         while self.ID in l:
            i=l.index(self.ID)
            del l[i]
      else:
         l.append(self.ID)
      self._app.config["disabled_plugins"]=l
   enabled=property(_get_enabled,_set_enabled)
   def _get_results_count(self):
      self.results_count_lock.acquire()
      res=self._results_count
      self.results_count_lock.release()
      return res
   def _set_results_count(self,value):
      self.results_count_lock.acquire()
      if type(value)==int:
         self._results_count=value
      self.results_count_lock.release()
   results_count=property(_get_results_count,_set_results_count)
   def stop(self):
      self.stop_search=True
      while not self.search_finished:
         time.sleep(0.1)
   def download(self,link):
      self._app.download(link)
   def _get_search_finished(self):
      self.search_finished_lock.acquire()
      res=self._search_finished
      self.search_finished_lock.release()
      return res
   def _set_search_finished(self,value):
      self.search_finished_lock.acquire()
      self._search_finished=value
      self.search_finished_lock.release()
   search_finished=property(_get_search_finished,_set_search_finished)
   def _get_stop_search(self):
      self.stop_search_lock.acquire()
      res=self._stop_search
      self.stop_search_lock.release()
      return res
   def _set_stop_search(self,value):
      self.stop_search_lock.acquire()
      self._stop_search=value
      self.stop_search_lock.release()
   stop_search=property(_get_stop_search,_set_stop_search)
   def _get_login_status(self):
      self.login_status_lock.acquire()
      res=self._login_status
      self.login_status_lock.release()
      return res
   def _set_login_status(self,value):
      self.login_status_lock.acquire()
      self._login_status=value
      self.login_status_lock.release()
   login_status=property(_get_login_status,_set_login_status)
   def search(self,pattern):
      self.new_results=ResultsList()
      self.search_finished=False
      self.stop_search=False
      self.results_count=-1
      self.login_status=LOGIN_STATUS_WAITING
      thread.start_new_thread(self._do_search,(pattern,))
      gobject.timeout_add(200,self._check_results)
      gobject.timeout_add(50,self._check_login_status)
   def _check_login_status(self):
      if self.login_status==LOGIN_STATUS_WAITING:
         return True
      if self.login_status==LOGIN_STATUS_FAILED:
         self._app.notify_plugin_login_failed(self)
      return False
   def _check_results(self):
      while len(self.new_results):
         item=self.new_results[0]
         item.plugin=self
         del self.new_results[0]
         self._app.add_result(self,item)
      if self.search_finished:
         self._app.notify_search_finished(self)
      return not self.search_finished
   def add_result(self,result):
      self.new_results.append(result)
   def _login_failed(self):
      self.login_status=LOGIN_STATUS_FAILED
   def _do_search(self,pattern):
      try:
         if self.require_auth:
            if self.login_cookie==None:
               self.login_cookie=self._try_login()
            if self.login_cookie==None:
               self._login_failed()
               return
         self.login_status=LOGIN_STATUS_OK
         self._run_search(pattern)
      except:
         pass
      self.search_finished=True
   def _run_search(self,pattern):
      pass

def check_plugin_dtd(tree,dtd,filename):
   ctxt=libxml2.newValidCtxt()
   messages=[]
   ctxt.setValidityErrorHandler(lambda item,msgs:msgs.append(item),None,messages)
   if tree.validateDtd(ctxt, dtd)!=1:
      msg=""
      for i in messages:
         msg+=i
      raise IncorrectPluginMetaFile(filename,msg)

def parse_metadata(app,filename):
   res={}
   dtd=libxml2.parseDTD(None, os.path.join(app.options.share_dir,UNIXNAME,"dtds","torrent-search-plugin.dtd"))
   tree=libxml2.parseFile(filename)
   check_plugin_dtd(tree,dtd,filename)
   root=tree.getRootElement()
   res["id"]=root.prop("id")
   res["version"]=root.prop("version")
   res["icon_url"]=None
   res["require_auth"]=False
   child=root.children
   while child:
      if child.name=="require_auth":
         res["require_auth"]=(child.getContent()=="true")
      elif child.type=="element":
         res[child.name]=child.getContent()
      child=child.next
   return res

def load_plugin(app,path):
   metadata_file=os.path.join(path,"metadata.xml")
   if not os.path.exists(metadata_file):
      raise PluginFileNotFound(metadata_file)
   if not os.path.isfile(metadata_file):
      raise PluginFileNotFile(metadata_file)
   if not os.access(metadata_file,os.R_OK):
      raise PluginFileNotReadable(metadata_file)
   metadata=parse_metadata(app,metadata_file)
   filename=os.path.join(path,metadata["filename"])
   if not os.path.exists(filename):
      raise PluginFileNotFound(filename)
   if not os.path.isfile(filename):
      raise PluginFileNotFile(filename)
   if not os.access(filename,os.R_OK):
      raise PluginFileNotReadable(filename)
   try:
      f=open(filename)
      m=imp.load_module(metadata["filename"][:-3],f,filename,('.py','r',imp.PY_SOURCE))
      plugin_class=getattr(m,metadata["classname"])
      res=plugin_class(app)
      res.TITLE=metadata['title']
      res.VERSION=metadata["version"]
      res.ID=metadata["id"]
      res.AUTHOR=metadata["author"]
      res.RELEASED_TIME=metadata["released_time"]
      res.icon_url=metadata["icon_url"]
      res.website_url=metadata["website_url"]
      res.require_auth=metadata["require_auth"]
      return res
   except:
      raise PluginSyntaxError(filename)
