The branch, eden has been updated
       via  3b410bcfc3fa931d70b78bf7a29da7057d47e34f (commit)
      from  b90a9f0b2f886d31c408971c3d9b7167ab722681 (commit)

- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/scripts;a=commit;h=3b410bcfc3fa931d70b78bf7a29da7057d47e34f

commit 3b410bcfc3fa931d70b78bf7a29da7057d47e34f
Author: Martijn Kaijser <[email protected]>
Date:   Wed May 21 19:53:49 2014 +0200

    [script.module.metahandler] 1.5.0

diff --git a/script.module.metahandler/addon.xml 
b/script.module.metahandler/addon.xml
index 3bc0b94..18d8fd0 100644
--- a/script.module.metahandler/addon.xml
+++ b/script.module.metahandler/addon.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <addon id="script.module.metahandler" 
      name="metahandler" 
-     version="1.4.0" 
+     version="1.5.0" 
      provider-name="Eldorado (inspired by anarchintosh, westcoast, daledude, 
t0mm0)">
   <requires>
     <import addon="xbmc.python" version="2.0" />
@@ -21,4 +21,4 @@
     <email></email>
     <source></source>
   </extension>
-</addon>
+</addon>
\ No newline at end of file
diff --git a/script.module.metahandler/changelog.txt 
b/script.module.metahandler/changelog.txt
index 35b497a..6e309ae 100644
--- a/script.module.metahandler/changelog.txt
+++ b/script.module.metahandler/changelog.txt
@@ -1,3 +1,13 @@
+[B]Version 1.5.0[/B]
+- Rework TMDB api key so that it can be set when initializing metahandlers - 
soon will require individual keys
+- Added option in addon settings to delete metahandlers cache db to restart 
fresh
+- Fixed refresh episode meta data to hold watched status
+- Updated tvdbapi calls for better accuracy
+- Added new config table to cache db - comes with new _set_config(setting, 
value) and _get_config(setting) methods
+- Get and set TMDB config values, cache in config table
+- Properly set TMDB image locations
+- Temporary SQL update added to clean existing DB's of hardcoded TMDB image 
url's - keep only filenames
+
 [B]Version 1.4.0[/B]
 - New TMDB API key
 - Updated calls to TMDB so we only make one request per movie
diff --git a/script.module.metahandler/lib/metahandler/TMDB.py 
b/script.module.metahandler/lib/metahandler/TMDB.py
index 2c8160f..8bf7a82 100644
--- a/script.module.metahandler/lib/metahandler/TMDB.py
+++ b/script.module.metahandler/lib/metahandler/TMDB.py
@@ -28,25 +28,16 @@ class TMDB(object):
     or if there is data missing on TMDB, another call is made to IMDB to fill 
in the missing information.       
     '''  
     
-    def __init__(self, api_key='af95ef8a4fe1e697f86b8c194f2e5e11', 
view='json', lang='en'):
+    def __init__(self, api_key='', view='json', lang='en'):
         #view = yaml json xml
         self.view = view
-        self.lang = self.__get_language(lang)
+        self.lang = lang
         self.api_key = api_key
         self.url_prefix = 'http://api.themoviedb.org/3'
-        self.poster_prefix = 'http://d3gtl9l2a4fn1j.cloudfront.net/t/p/' + 
addon.get_setting('tmdb_poster_size')
-        self.backdrop_prefix = 'http://d3gtl9l2a4fn1j.cloudfront.net/t/p/' + 
addon.get_setting('tmdb_backdrop_size')
         self.imdb_api = 'http://www.imdbapi.com/?i=%s'
         self.imdb_name_api = 'http://www.imdbapi.com/?t=%s'
         self.imdb_nameyear_api = 'http://www.imdbapi.com/?t=%s&y=%s' 
-
-    def __get_language(self, lang):
-        tmdb_language = addon.get_setting('tmdb_language')
-        if tmdb_language:
-            return re.sub(".*\((\w+)\).*","\\1",tmdb_language)
-        else:
-            return lang
-
+      
     def __clean_name(self, mystring):
         newstring = ''
         for word in mystring.split(' '):
@@ -139,7 +130,15 @@ class TMDB(object):
             except:
                 return True
 
+                
+    def call_config(self):
+        '''
+        Query TMDB config api for current values
+        '''
+        r = self._do_request('configuration', '')
+        return r        
 
+        
     def search_imdb(self, name, imdb_id='', year=''):
         '''
         Search IMDB by either IMDB ID or Name/Year      
@@ -396,9 +395,9 @@ class TMDB(object):
                 trailers = meta['trailers']
                 
                 if meta.has_key('poster_path') and meta['poster_path']:
-                    meta['cover_url'] = self.poster_prefix + 
meta['poster_path']
+                    meta['cover_url'] = meta['poster_path']
                 if meta.has_key('backdrop_path') and meta['backdrop_path']:
-                    meta['backdrop_url'] = self.backdrop_prefix + 
meta['backdrop_path']
+                    meta['backdrop_url'] = meta['backdrop_path']
                 meta['released'] = meta['release_date']
                 #Set rating to 0 so that we can force it to be grabbed from 
IMDB
                 meta['tmdb_rating'] = meta['vote_average']
diff --git a/script.module.metahandler/lib/metahandler/metahandlers.py 
b/script.module.metahandler/lib/metahandler/metahandlers.py
index 482929f..9c309a1 100644
--- a/script.module.metahandler/lib/metahandler/metahandlers.py
+++ b/script.module.metahandler/lib/metahandler/metahandlers.py
@@ -34,7 +34,7 @@ from thetvdbapi import TheTVDB
 import xbmc
 import xbmcvfs
 
-''' Use t0mm0's common library for http calls '''
+''' Use t0mm0.common library for http calls '''
 from t0mm0.common.net import Net
 net = Net()
 
@@ -95,15 +95,19 @@ class MetaData:
     '''  
 
      
-    def __init__(self, preparezip=False):
+    def __init__(self, preparezip=False, 
tmdb_api_key='af95ef8a4fe1e697f86b8c194f2e5e11'):
 
         #Check if a path has been set in the addon settings
         settings_path = common.addon.get_setting('meta_folder_location')
         
+        # TMDB constants
+        self.tmdb_image_url = ''
+        self.tmdb_api_key = tmdb_api_key
+               
         if settings_path:
             self.path = xbmc.translatePath(settings_path)
         else:
-            self.path = 
xbmc.translatePath('special://profile/addon_data/script.module.metahandler')
+            self.path = common.profile_path();
         
         self.cache_path = make_dir(self.path, 'meta_cache')
 
@@ -147,7 +151,7 @@ class MetaData:
             db_user = common.addon.get_setting('db_user')
             db_pass = common.addon.get_setting('db_pass')
             db_name = common.addon.get_setting('db_name')
-            self.dbcon = database.connect(db_name, db_user, db_pass, 
db_address, buffered=True)
+            self.dbcon = database.connect(database=db_name, user=db_user, 
password=db_pass, host=db_address, buffered=True)
             self.dbcur = self.dbcon.cursor(cursor_class=MySQLCursorDict, 
buffered=True)
         else:
             self.dbcon = database.connect(self.videocache)
@@ -156,7 +160,70 @@ class MetaData:
 
         # initialize cache db
         self._cache_create_movie_db()
+        
+        # Check TMDB configuration, update if necessary
+        self._set_tmdb_config()
+
+        ## !!!!!!!!!!!!!!!!!! Temporary code to update movie_meta columns 
cover_url and backdrop_url to store only filename !!!!!!!!!!!!!!!!!!!!!
+ 
+        ## We have matches with outdated url, so lets strip the url out and 
only keep filename 
+        try:
 
+            if DB == 'mysql':
+                sql_select = "SELECT imdb_id, tmdb_id, cover_url, backdrop_url 
"\
+                                "FROM movie_meta "\
+                                "where substring(cover_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net' "\
+                                "or substring(backdrop_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net'"
+                self.dbcur.execute(sql_select)
+                matchedrows = self.dbcur.fetchall()[0]
+                
+                if matchedrows:
+                    sql_update = "UPDATE movie_meta "\
+                                    "SET cover_url = 
SUBSTRING_INDEX(cover_url, '/', -1), "\
+                                    "backdrop_url = 
SUBSTRING_INDEX(backdrop_url, '/', -1) "\
+                                    "where substring(cover_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net' "\
+                                    "or substring(backdrop_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net'"
+                    self.dbcur.execute(sql_update)
+                    self.dbcon.commit()
+                    common.addon.log('MySQL rows successfully updated')
+
+                else:
+                    common.addon.log('No MySQL rows requiring update')         
           
+                
+            else:
+          
+                sql_select = "SELECT imdb_id, tmdb_id, cover_url, backdrop_url 
"\
+                                "FROM movie_meta "\
+                                "where substr(cover_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net' "\
+                                "or substr(backdrop_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net'"
+                self.dbcur.execute(sql_select)
+                matchedrows = self.dbcur.fetchone()
+
+                if matchedrows:
+                    sql_update = "update movie_meta "\
+                                    "set cover_url = "\
+                                        "case when substr(cover_url, 
length(cover_url) - 31, 1) = '/' "\
+                                        "   then substr(cover_url, 
length(cover_url) - 31, 32) "\
+                                        "else substr(cover_url, 
length(cover_url) - 30, 31) "\
+                                        "end, "\
+                                        "backdrop_url = "\
+                                        "case when substr(backdrop_url, 
length(backdrop_url) - 31, 1) = '/' "\
+                                        "   then substr(backdrop_url, 
length(backdrop_url) - 31, 32) "\
+                                        "else substr(backdrop_url, 
length(backdrop_url) - 30, 31) "\
+                                        "end "\
+                                        "where substr(cover_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net' "\
+                                        "or substr(backdrop_url, 1, 36 ) = 
'http://d3gtl9l2a4fn1j.cloudfront.net'"
+                    self.dbcur.execute(sql_update)
+                    self.dbcon.commit()
+                    common.addon.log('SQLite rows successfully updated')
+                else:
+                    common.addon.log('No SQLite rows requiring update')        
            
+               
+        except Exception, e:
+            common.addon.log('************* Error updating cover and backdrop 
columns: %s' % e, 4)
+            pass
+        
+        ## 
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
     def __del__(self):
         ''' Cleanup db when object destroyed '''
@@ -307,6 +374,21 @@ class MetaData:
             self.dbcur.execute(sql_create)
         common.addon.log('Table addons initialized', 0)
 
+        # Create Configuration table
+        sql_create = "CREATE TABLE IF NOT EXISTS config ("\
+                           "setting TEXT, "\
+                           "value TEXT, "\
+                           "UNIQUE(setting)"\
+                           ");"
+
+        if DB == 'mysql':
+            sql_create = sql_create.replace("setting TEXT", "setting 
VARCHAR(255)")
+            sql_create = sql_create.replace("value TEXT", "value VARCHAR(255)")
+            self.dbcur.execute(sql_create)
+        else:
+            self.dbcur.execute(sql_create)
+        common.addon.log('Table config initialized', 0)
+        
 
     def _init_movie_meta(self, imdb_id, tmdb_id, name, year=0):
         '''
@@ -444,6 +526,22 @@ class MetaData:
         return meta
         
 
+    def __get_tmdb_language(self):
+        tmdb_language = common.addon.get_setting('tmdb_language')
+        if tmdb_language:
+            return re.sub(".*\((\w+)\).*","\\1",tmdb_language)
+        else:
+            return 'en'
+
+            
+    def __get_tvdb_language(self) :
+        tvdb_language = common.addon.get_setting('tvdb_language')
+        if tvdb_language and tvdb_language!='':
+            return re.sub(".*\((\w+)\).*","\\1",tvdb_language)
+        else:
+            return 'en'
+
+            
     def _string_compare(self, s1, s2):
         """ Method that takes two strings and returns True or False, based
             on if they are equal, regardless of case.
@@ -589,6 +687,91 @@ class MetaData:
             return 0
 
 
+    def _get_config(self, setting):
+        '''
+        Query local Config table for values
+        '''
+        
+        #Query local table first for current values
+        sql_select = "SELECT * FROM config where setting = '%s'" % setting
+
+        common.addon.log('Looking up in local cache for config data: %s' % 
setting, 2)
+        common.addon.log('SQL Select: %s' % sql_select, 0)        
+
+        try:    
+            self.dbcur.execute(sql_select)
+            matchedrow = self.dbcur.fetchone()
+        except Exception, e:
+            common.addon.log('************* Error selecting from cache db: %s' 
% e, 4)
+            return None
+
+        if matchedrow:
+            common.addon.log('Found config data in cache table for setting: %s 
value: %s' % (setting, dict(matchedrow)), 0)
+            return dict(matchedrow)['value']
+        else:
+            common.addon.log('No match in local DB for config setting: %s' % 
setting, 0)
+            return None            
+            
+
+    def _set_config(self, setting, value):
+        '''
+        Set local Config table for values
+        '''
+        
+        try:
+            sql_insert = "REPLACE INTO config (setting, value) VALUES(%s,%s)"
+            if DB == 'sqlite':
+                sql_insert = 'INSERT OR ' + sql_insert.replace('%s', '?')
+
+            common.addon.log('Updating local cache for config data: %s value: 
%s' % (setting, value), 2)
+            common.addon.log('SQL Insert: %s' % sql_insert, 0)                 
+
+            self.dbcur.execute(sql_insert, (setting, value))
+            self.dbcon.commit()
+        except Exception, e:
+            common.addon.log('************* Error updating cache db: %s' % e, 
4)
+            return None
+            
+
+    def _set_tmdb_config(self):
+        '''
+        Query config database for required TMDB config values, set constants 
as needed
+        Validate cache timestamp to ensure it is only refreshed once every 7 
days
+        '''
+    
+        tmdb_image_url = self._get_config('tmdb_image_url')
+        tmdb_config_timestamp = self._get_config('tmdb_config_timestamp')
+        
+        #Grab current time in seconds            
+        now = time.time()
+        age = 0
+
+        #Cache limit is 7 days: 60 seconds * 60 minutes * 24 hours * 7 days
+        expire = 60 * 60 * 24 * 7
+              
+        #Check if image and timestamp values are valid
+        if tmdb_image_url and tmdb_config_timestamp:
+            created = float(tmdb_config_timestamp)
+            age = now - created
+            
+            #If cache hasn't expired, set constant values
+            if age < expire:
+                common.addon.log('Cache still valid, setting values', 0)
+                self.tmdb_image_url = tmdb_image_url
+        
+        #Either we don't have the values or the cache has expired, so lets 
request and set them - update cache in the end
+        elif not tmdb_image_url or not tmdb_config_timestamp or age > expire:
+            common.addon.log('No cached config data found or cache expired, 
requesting from TMDB', 0)
+
+            tmdb = TMDB(api_key=self.tmdb_api_key, 
lang=self.__get_tmdb_language())
+            config_data = tmdb.call_config()
+
+            if config_data:
+                self.tmdb_image_url = config_data['images']['base_url']
+                self._set_config('tmdb_image_url', 
config_data['images']['base_url'])
+                self._set_config('tmdb_config_timestamp', now)
+                
+
     def check_meta_installed(self, addon_id):
         '''
         Check if a meta data pack has been installed for a specific addon
@@ -830,7 +1013,15 @@ class MetaData:
                         banner_path=os.path.join(root_banners, 
banner_name[0].lower())
                         if self.classmode == 'true':
                             self._downloadimages(meta['banner_url'], 
banner_path, banner_name)
-                        meta['banner_url'] = os.path.join(banner_path, 
banner_name)        
+                        meta['banner_url'] = os.path.join(banner_path, 
banner_name)
+
+        #Else - they are online so piece together the full URL from TMDB 
+        else:
+            if media_type == self.type_movie:
+                if meta['cover_url'].startswith('/'):
+                    meta['cover_url'] = self.tmdb_image_url  + 
common.addon.get_setting('tmdb_poster_size') + meta['cover_url']
+                if meta['backdrop_url'].startswith('/'):                    
+                    meta['backdrop_url'] = self.tmdb_image_url  + 
common.addon.get_setting('tmdb_backdrop_size') + meta['backdrop_url']
 
         common.addon.log('Returned Meta: %s' % meta, 0)
         return meta  
@@ -1129,7 +1320,7 @@ class MetaData:
             these "None found" entries otherwise we hit tmdb alot.
         '''        
         
-        tmdb = TMDB()        
+        tmdb = TMDB(api_key=self.tmdb_api_key, lang=self.__get_tmdb_language())
         meta = tmdb.tmdb_lookup(name,imdb_id,tmdb_id, year)
         
         if meta is None:
@@ -1270,7 +1461,7 @@ class MetaData:
             these "None found" entries otherwise we hit tvdb alot.
         '''      
         common.addon.log('Starting TVDB Lookup', 0)
-        tvdb = TheTVDB()
+        tvdb = TheTVDB(language=self.__get_tvdb_language())
         tvdb_id = ''
         
         try:
@@ -1615,7 +1806,7 @@ class MetaData:
         else:
             return None
 
-
+     
     def update_episode_meta(self, name, imdb_id, season, episode, tvdb_id='', 
new_imdb_id='', new_tvdb_id=''):
         '''
         Updates and returns meta data for given episode, 
@@ -1663,7 +1854,7 @@ class MetaData:
         elif not new_tvdb_id:
             new_tvdb_id = tvdb_id
             
-        return self.get_episode_meta(name, imdb_id, season, episode, overlay)
+        return self.get_episode_meta(name, imdb_id, season, episode, 
overlay=overlay)
 
 
     def _cache_lookup_episode(self, imdb_id, tvdb_id, season, episode, 
air_date=''):
@@ -1777,7 +1968,7 @@ class MetaData:
         '''      
         
         meta = {}
-        tvdb = TheTVDB()
+        tvdb = TheTVDB(language=self._get_tvdb_language())
         if air_date:
             try:
                 episode = tvdb.get_episode_by_airdate(tvdb_id, air_date)
@@ -2246,7 +2437,7 @@ class MetaData:
 
 
     def _get_season_posters(self, tvdb_id, season):
-        tvdb = TheTVDB()
+        tvdb = TheTVDB(language=self._get_tvdb_language())
         
         try:
             images = tvdb.get_show_image_choices(tvdb_id)
diff --git a/script.module.metahandler/lib/metahandler/thetvdbapi.py 
b/script.module.metahandler/lib/metahandler/thetvdbapi.py
index 6f3dabe..6b79567 100644
--- a/script.module.metahandler/lib/metahandler/thetvdbapi.py
+++ b/script.module.metahandler/lib/metahandler/thetvdbapi.py
@@ -1,6 +1,7 @@
 """
 thetvdb.com Python API
 (c) 2009 James Smith (http://loopj.com)
+(c) 2014 Wayne Davison <[email protected]>
 
 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
@@ -18,135 +19,189 @@ along with this program.  If not, see 
<http://www.gnu.org/licenses/>.
 
 import urllib
 import datetime
+import random
 import re
 import copy
 
-import xml.etree.ElementTree as ET 
-#import elementtree.ElementTree as ET
-#from elementtree.ElementTree import ElementTree as ET
+import xml.parsers.expat as expat
 from cStringIO import StringIO
+from zipfile import ZipFile
 
 class TheTVDB(object):
-    def __init__(self, api_key='2B8557E0CBF7D720'):
-        
#http://www.thetvdb.com/api/GetEpisodeByAirDate.php?apikey=1D62F2F90030C444&seriesid=71256&airdate=2010-03-29
+    def __init__(self, api_key='2B8557E0CBF7D720', language = 'en', want_raw = 
False):
+        #http://thetvdb.com/api/<apikey>/<request>
         self.api_key = api_key
-        self.mirror_url = "http://www.thetvdb.com";
+        self.mirror_url = "http://thetvdb.com";
         self.base_url =  self.mirror_url + "/api"
         self.base_key_url = "%s/%s" % (self.base_url, self.api_key)
-        
+        self.language = language
+        self.want_raw = want_raw
+
+        # Mirror selection got deprecated a while back, so tell it to skip the 
actual fetch.
+        self.select_mirrors(False)
+
+
+    def select_mirrors(self, do_the_fetch = True):
+        #http://thetvdb.com/api/<apikey>/mirrors.xml
+        url = "%s/mirrors.xml" % self.base_key_url
+        self.xml_mirrors = []
+        self.zip_mirrors = []
+        try:
+            filt_func = lambda name, attrs: attrs if name == 'Mirror' else None
+            xml = self._get_xml_data(url, filt_func) if do_the_fetch else {}
+            for mirror in xml.get("Mirror", []):
+                mirrorpath = mirror.get("mirrorpath", None)
+                typemask = mirror.get("typemask", None)
+                if not mirrorpath or not typemask:
+                    continue
+                typemask = int(typemask)
+                if typemask & 1:
+                    self.xml_mirrors.append(mirrorpath)
+                if typemask & 4:
+                    self.zip_mirrors.append(mirrorpath)
+        except:
+            pass
+
+        if not self.xml_mirrors:
+            self.xml_mirrors = [ self.mirror_url ]
+        if not self.zip_mirrors:
+            self.zip_mirrors = [ self.mirror_url ]
+
+        self.xml_mirror_url = random.choice(self.xml_mirrors)
+        self.zip_mirror_url = random.choice(self.zip_mirrors)
+
+        self.base_xml_url = "%s/api/%s" % (self.xml_mirror_url, self.api_key)
+        self.base_zip_url = "%s/api/%s" % (self.zip_mirror_url, self.api_key)
+
+
+    def _2show(self, attrs):
+        return attrs if self.want_raw else TheTVDB.Show(attrs, self.mirror_url)
+
+
+    def _2episode(self, attrs):
+        return attrs if self.want_raw else TheTVDB.Episode(attrs, 
self.mirror_url)
+
+
     class Show(object):
         """A python object representing a thetvdb.com show record."""
         def __init__(self, node, mirror_url):
             # Main show details
-            #print node
-            self.id = node.findtext("id")
-            self.name = node.findtext("SeriesName")
-            self.overview = node.findtext("Overview")
-            self.genre = node.findtext("Genre") #[g for g in 
node.findtext("Genre").split("|") if g]
-            self.actors = [a for a in node.findtext("Actors").split("|") if a]
-            self.network = node.findtext("Network")
-            self.content_rating = node.findtext("ContentRating")
-            self.rating = node.findtext("Rating")
-            self.runtime = node.findtext("Runtime")
-            self.status = node.findtext("Status")
-            self.language = node.findtext("Language")
-        
+            self.id = node.get("id", "")
+            self.name = node.get("SeriesName", "")
+            self.overview = node.get("Overview", "")
+            self.genre = node.get("Genre", "") #[g for g in node.get("Genre", 
"").split("|") if g]
+            self.actors = [a for a in node.get("Actors", "").split("|") if a]
+            self.network = node.get("Network", "")
+            self.content_rating = node.get("ContentRating", "")
+            self.rating = node.get("Rating", "")
+            self.runtime = node.get("Runtime", "")
+            self.status = node.get("Status", "")
+            self.language = node.get("Language", "")
+
             # Air details
-            self.first_aired = 
TheTVDB.convert_date(node.findtext("FirstAired"))
-            self.airs_day = node.findtext("Airs_DayOfWeek")
-            self.airs_time = 
node.findtext("Airs_Time")#TheTVDB.convert_time(node.findtext("Airs_Time"))
-        
+            self.first_aired = TheTVDB.convert_date(node.get("FirstAired", ""))
+            self.airs_day = node.get("Airs_DayOfWeek", "")
+            self.airs_time = node.get("Airs_Time", 
"")#TheTVDB.convert_time(node.get("Airs_Time", ""))
+
             # Main show artwork
-            temp = node.findtext("banner")
+            temp = node.get("banner", "")
             if temp != '' and temp is not None:
                 self.banner_url = "%s/banners/%s" % (mirror_url, temp)
             else:
                 self.banner_url = ''
-            temp = node.findtext("poster")
+            temp = node.get("poster", "")
             if temp != '' and temp is not None:
                 self.poster_url = "%s/banners/%s" % (mirror_url, temp)
             else:
                 self.poster_url = ''
-            temp = node.findtext("fanart")
+            temp = node.get("fanart", "")
             if temp != '' and temp is not None:
                 self.fanart_url = "%s/banners/%s" % (mirror_url, temp)
             else:
                 self.fanart_url = ''
 
             # External references
-            self.imdb_id = node.findtext("IMDB_ID")
-            self.tvcom_id = node.findtext("SeriesID")
-            self.zap2it_id = node.findtext("zap2it_id")
+            self.imdb_id = node.get("IMDB_ID", "")
+            self.tvcom_id = node.get("SeriesID", "")
+            self.zap2it_id = node.get("zap2it_id", "")
 
             # When this show was last updated
-            self.last_updated = 
datetime.datetime.fromtimestamp(int(node.findtext("lastupdated")))
+            self.last_updated_utime = 
int(TheTVDB.check(node.get("lastupdated", ""), '0'))
+            self.last_updated = 
datetime.datetime.fromtimestamp(self.last_updated_utime)
+
 
         def __str__(self):
             import pprint
             return pprint.saferepr(self)
 
+
     class Episode(object):
         """A python object representing a thetvdb.com episode record."""
         def __init__(self, node, mirror_url):
-            self.id = node.findtext("id")
-            self.show_id = node.findtext("seriesid")
-            self.name = node.findtext("EpisodeName")
-            self.overview = node.findtext("Overview")
-            self.season_number = node.findtext("SeasonNumber")
-            self.episode_number = node.findtext("EpisodeNumber")
-            self.director = node.findtext("Director")
-            self.guest_stars = node.findtext("GuestStars")
-            self.language = node.findtext("Language")
-            self.production_code = node.findtext("ProductionCode")
-            self.rating = node.findtext("Rating")
-            self.writer = node.findtext("Writer")
+            self.id = node.get("id", "")
+            self.show_id = node.get("seriesid", "")
+            self.name = node.get("EpisodeName", "")
+            self.overview = node.get("Overview", "")
+            self.season_number = node.get("SeasonNumber", "")
+            self.episode_number = node.get("EpisodeNumber", "")
+            self.director = node.get("Director", "")
+            self.guest_stars = node.get("GuestStars", "")
+            self.language = node.get("Language", "")
+            self.production_code = node.get("ProductionCode", "")
+            self.rating = node.get("Rating", "")
+            self.writer = node.get("Writer", "")
 
             # Air date
-            self.first_aired = 
node.findtext("FirstAired")#TheTVDB.convert_date(node.findtext("FirstAired"))
+            self.first_aired = node.get("FirstAired", 
"")#TheTVDB.convert_date(node.get("FirstAired", ""))
 
             # DVD Information
-            self.dvd_chapter = node.findtext("DVD_chapter")
-            self.dvd_disc_id = node.findtext("DVD_discid")
-            self.dvd_episode_number = node.findtext("DVD_episodenumber")
-            self.dvd_season = node.findtext("DVD_season")
+            self.dvd_chapter = node.get("DVD_chapter", "")
+            self.dvd_disc_id = node.get("DVD_discid", "")
+            self.dvd_episode_number = node.get("DVD_episodenumber", "")
+            self.dvd_season = node.get("DVD_season", "")
 
             # Artwork/screenshot
-            temp = node.findtext("filename")
+            temp = node.get("filename", "")
             if temp != '' and temp is not None:
                 self.image = "%s/banners/%s" % (mirror_url, temp)
             else:
                 self.image = ''
-            #self.image = 'http://thetvdb.com/banners/' + 
node.findtext("filename")
+            #self.image = 'http://thetvdb.com/banners/' + node.get("filename", 
"")
 
             # Episode ordering information (normally for specials)
-            self.airs_after_season = node.findtext("airsafter_season")
-            self.airs_before_season = node.findtext("airsbefore_season")
-            self.airs_before_episode = node.findtext("airsbefore_episode")
+            self.airs_after_season = node.get("airsafter_season", "")
+            self.airs_before_season = node.get("airsbefore_season", "")
+            self.airs_before_episode = node.get("airsbefore_episode", "")
 
             # Unknown
-            self.combined_episode_number = 
node.findtext("combined_episode_number")
-            self.combined_season = node.findtext("combined_season")
-            self.absolute_number = node.findtext("absolute_number")
-            self.season_id = node.findtext("seasonid")
-            self.ep_img_flag = node.findtext("EpImgFlag")
+            self.combined_episode_number = node.get("Combined_episodenumber", 
"")
+            self.combined_season = node.get("Combined_season", "")
+            self.absolute_number = node.get("absolute_number", "")
+            self.season_id = node.get("seasonid", "")
+            self.ep_img_flag = node.get("EpImgFlag", "")
 
             # External references
-            self.imdb_id = node.findtext("IMDB_ID")
+            self.imdb_id = node.get("IMDB_ID", "")
 
             # When this episode was last updated
-            self.last_updated = 
datetime.datetime.fromtimestamp(int(self.check(node.findtext("lastupdated"), 
'0')))
+            self.last_updated_utime = 
int(TheTVDB.check(node.get("lastupdated", ""), '0'))
+            self.last_updated = 
datetime.datetime.fromtimestamp(self.last_updated_utime)
+
 
         def __str__(self):
             return repr(self)
 
-        def check(self, value, ret=None):
-            if value is None or value == '':
-                if ret == None:
-                    return ''
-                else:
-                    return ret
+
+    @staticmethod
+    def check(value, ret=None):
+        if value is None or value == '':
+            if ret == None:
+                return ''
             else:
-                return value
+                return ret
+        else:
+            return value
+
 
     @staticmethod
     def convert_time(time_string):
@@ -161,12 +216,16 @@ class TheTVDB(object):
 
                 if "hour" in gd and "minute" in gd and gd["minute"] and "ampm" 
in gd:
                     hour = int(gd["hour"])
+                    if hour == 12:
+                        hour = 0
                     if gd["ampm"].lower() == "p":
                         hour += 12
 
                     return datetime.time(hour, int(gd["minute"]))
                 elif "hour" in gd and "ampm" in gd:
                     hour = int(gd["hour"])
+                    if hour == 12:
+                        hour = 0
                     if gd["ampm"].lower() == "p":
                         hour += 12
 
@@ -176,6 +235,7 @@ class TheTVDB(object):
 
         return None
 
+
     @staticmethod
     def convert_date(date_string):
         """Convert a thetvdb date string into a datetime.date object."""
@@ -187,195 +247,194 @@ class TheTVDB(object):
 
         return first_aired
 
-    def get_matching_shows(self, show_name):
+
+    # language can be "all", "en", "fr", etc.
+    def get_matching_shows(self, show_name, language=None, want_raw=False):
         """Get a list of shows matching show_name."""
-        get_args = urllib.urlencode({"seriesname": show_name}, doseq=True)
+        if type(show_name) == type(u''):
+            show_name = show_name.encode('utf-8')
+        get_args = {"seriesname": show_name}
+        if language is not None:
+            get_args['language'] = language
+        get_args = urllib.urlencode(get_args, doseq=True)
         url = "%s/GetSeries.php?%s" % (self.base_url, get_args)
-        print url
-        data = urllib.urlopen(url)
-        show_list = []
-        if data:
-            try:
-                tree = ET.parse(data)
-                show_list = [(show.findtext("seriesid"), 
show.findtext("SeriesName"),show.findtext("IMDB_ID")) for show in 
tree.getiterator("Series")]
-            except SyntaxError:
-                pass
+        if want_raw:
+            filt_func = lambda name, attrs: attrs if name == "Series" else None
+        else:
+            filt_func = lambda name, attrs: (attrs.get("seriesid", ""), 
attrs.get("SeriesName", ""), attrs.get("IMDB_ID", "")) if name == "Series" else 
None
+        xml = self._get_xml_data(url, filt_func)
+        return xml.get('Series', [])
 
-        return show_list
 
     def get_show(self, show_id):
         """Get the show object matching this show_id."""
-        #url = "%s/series/%s/%s.xml" % (self.base_key_url, show_id, "el")
-        url = "%s/series/%s/" % (self.base_key_url, show_id)
-        data = StringIO(urllib.urlopen(url).read())
-        temp_data = data.getvalue()
-        show = None
-        if temp_data.startswith('<?xml') == False:
-            return show
-
-        try:
-            tree = ET.parse(data)
-            show_node = tree.find("Series")
-
-            show = TheTVDB.Show(show_node, self.mirror_url)
-        except SyntaxError:
-            pass
-
-        return show
+        url = "%s/series/%s/%s.xml" % (self.base_xml_url, show_id, 
self.language)
+        return self._get_show_by_url(url)
 
 
     def get_show_by_imdb(self, imdb_id):
         """Get the show object matching this show_id."""
         #url = "%s/series/%s/%s.xml" % (self.base_key_url, show_id, "el")
         url = "%s/GetSeriesByRemoteID.php?imdbid=%s" % (self.base_url, imdb_id)
-        data = StringIO(urllib.urlopen(url).read())
-        temp_data = data.getvalue()
-        tvdb_id = ''
-        if temp_data.startswith('<?xml') == False:
-            return tvdb_id
-        try:
-            tree = ET.parse(data)
-            for show in tree.getiterator("Series"):
-                tvdb_id = show.findtext("seriesid")
+        show = self._get_show_by_url(url)
+        if show:
+            return show['id'] if self.want_raw else show.id
+        return ''
 
-        except SyntaxError:
-            pass
 
-        return tvdb_id
+    def _get_show_by_url(self, url):
+        filt_func = lambda name, attrs: self._2show(attrs) if name == "Series" 
else None
+        xml = self._get_xml_data(url, filt_func)
+        return xml['Series'][0] if 'Series' in xml else None
+
 
     def get_episode(self, episode_id):
         """Get the episode object matching this episode_id."""
-        url = "%s/episodes/%s" % (self.base_key_url, episode_id)
-        data = urllib.urlopen(url)
-        print url
-        episode = None
-        try:
-            tree = ET.parse(data)
-            episode_node = tree.find("Episode")
-
-            episode = TheTVDB.Episode(episode_node, self.mirror_url)
-        except SyntaxError:
-            pass
-
-        return episode
+        url = "%s/episodes/%s" % (self.base_xml_url, episode_id)
+        return self._get_episode_by_url(url)
 
 
     def get_episode_by_airdate(self, show_id, aired):
         """Get the episode object matching this episode_id."""
         #url = "%s/series/%s/default/%s/%s" % (self.base_key_url, show_id, 
season_num, ep_num)
         
'''http://www.thetvdb.com/api/GetEpisodeByAirDate.php?apikey=1D62F2F90030C444&seriesid=71256&airdate=2010-03-29'''
-
         url = 
"%s/GetEpisodeByAirDate.php?apikey=1D62F2F90030C444&seriesid=%s&airdate=%s" % 
(self.base_url, show_id, aired)
-        data = StringIO(urllib.urlopen(url).read())
-        
-        print url
-        episode = None
-        
-        #code to check if data has been returned
-        temp_data = data.getvalue()
-        if temp_data.startswith('<?xml') == False:
-            print 'No data returned ', temp_data
-            return episode
-        
-        try:
-            tree = ET.parse(data)
-            episode_node = tree.find("Episode")
+        return self._get_episode_by_url(url)
 
-            episode = TheTVDB.Episode(episode_node, self.mirror_url)
-            
-        except SyntaxError:
-            pass
-        
-        return episode
-  
 
     def get_episode_by_season_ep(self, show_id, season_num, ep_num):
         """Get the episode object matching this episode_id."""
-        url = "%s/series/%s/default/%s/%s" % (self.base_key_url, show_id, 
season_num, ep_num)
-        data = StringIO(urllib.urlopen(url).read())
-
-        episode = None
-        print url
-        
-        #code to check if data has been returned
-        temp_data = data.getvalue()
-        if temp_data.startswith('<?xml') == False :
-            print 'No data returned ', temp_data
-            return episode
-        
-        try:
-            tree = ET.parse(data)
-            episode_node = tree.find("Episode")
+        url = "%s/series/%s/default/%s/%s" % (self.base_xml_url, show_id, 
season_num, ep_num)
+        return self._get_episode_by_url(url)
+
+
+    def _get_episode_by_url(self, url):
+        filt_func = lambda name, attrs: self._2episode(attrs) if name == 
"Episode" else None
+        xml = self._get_xml_data(url, filt_func)
+        return xml['Episode'][0] if 'Episode' in xml else None
+
 
-            episode = TheTVDB.Episode(episode_node, self.mirror_url)
-        except SyntaxError:
-            pass
-        
-        return episode
-   
     def get_show_and_episodes(self, show_id):
         """Get the show object and all matching episode objects for this 
show_id."""
-        url = "%s/series/%s/all/" % (self.base_key_url, show_id)
-        data = urllib.urlopen(url)
-        
-        show_and_episodes = None
-        try:
-            tree = ET.parse(data)
-            show_node = tree.find("Series")
-        
-            show = TheTVDB.Show(show_node, self.mirror_url)
-            episodes = []
-        
-            episode_nodes = tree.getiterator("Episode")
-            for episode_node in episode_nodes:
-                episodes.append(TheTVDB.Episode(episode_node, self.mirror_url))
-        
-            show_and_episodes = (show, episodes)
-        except SyntaxError:
-            pass
-        
-        return show_and_episodes
+        url = "%s/series/%s/all/%s.zip" % (self.base_zip_url, show_id, 
self.language)
+        zip_name = '%s.xml' % self.language
+        filt_func = lambda name, attrs: self._2episode(attrs) if name == 
"Episode" else self._2show(attrs) if name == "Series" else None
+        xml = self._get_xml_data(url, filt_func, zip_name=zip_name)
+        if 'Series' not in xml:
+            return None
+        return (xml['Series'][0], xml.get('Episode', []))
+
+
+    def get_show_image_choices(self, show_id):
+        """Get a list of image urls and types relating to this show."""
+        url = "%s/series/%s/banners.xml" % (self.base_xml_url, show_id)
+        filt_func = lambda name, attrs: attrs if name == "Banner" else None
+        xml = self._get_xml_data(url, filt_func)
+
+        images = []
+        for banner in xml.get("Banner", []):
+            banner_type = banner["BannerType"]
+            banner_season = banner["Season"] if banner_type == 'season' else ''
+            banner_url = "%s/banners/%s" % (self.mirror_url, 
banner["BannerPath"])
+            images.append((banner_url, banner_type, banner_season))
+
+        return images
+
 
     def get_updated_shows(self, period = "day"):
         """Get a list of show ids which have been updated within this 
period."""
-        url = "%s/updates/updates_%s.xml" % (self.base_key_url, period)
-        data = urllib.urlopen(url)
-        tree = ET.parse(data)
-
-        series_nodes = tree.getiterator("Series")
+        filt_func = lambda name, attrs: attrs["id"] if name == "Series" else 
None
+        xml = self._get_update_info(period, filt_func)
+        return xml.get("Series", [])
 
-        return [x.findtext("id") for x in series_nodes]
 
     def get_updated_episodes(self, period = "day"):
         """Get a list of episode ids which have been updated within this 
period."""
-        url = "%s/updates/updates_%s.xml" % (self.base_key_url, period)
-        data = urllib.urlopen(url)
-        tree = ET.parse(data)
+        filt_func = lambda name, attrs: (attrs["Series"], attrs["id"]) if name 
== "Episode" else None
+        xml = self._get_update_info(period, filt_func)
+        return xml.get("Episode", [])
 
-        episode_nodes = tree.getiterator("Episode")
 
-        return [(x.findtext("Series"), x.findtext("id")) for x in 
episode_nodes]
+    def get_updates(self, callback, period = "day"):
+        """Return all series, episode, and banner updates w/o having to have it
+        all in memory at once.  Also returns the Data timestamp.  The callback
+        routine should be defined as: my_callback(name, attrs) where name will
+        be "Data", "Series", "Episode", or "Banner", and attrs will be a dict
+        of the values (e.g. id, time, etc)."""
+        self._get_update_info(period, callback=callback)
 
-    def get_show_image_choices(self, show_id):
-        """Get a list of image urls and types relating to this show."""
-        url = "%s/series/%s/banners.xml" % (self.base_key_url, show_id)
-        data = urllib.urlopen(url)
-        tree = ET.parse(data)
-        print url
-        
-        images = []
 
-        banner_data = tree.find("Banners")
-        banner_nodes = tree.getiterator("Banner")
-        for banner in banner_nodes:
-            banner_path = banner.findtext("BannerPath")
-            banner_type = banner.findtext("BannerType")
-            if banner_type == 'season':
-                banner_season = banner.findtext("Season")
-            else:
-                banner_season = ''
-            banner_url = "%s/banners/%s" % (self.mirror_url, banner_path)
+    def _get_update_info(self, period, filter_func = None, callback = None):
+        url = "%s/updates/updates_%s.zip" % (self.base_zip_url, period)
+        zip_name = 'updates_%s.xml' % period
+        return self._get_xml_data(url, filter_func, zip_name, callback)
 
-            images.append((banner_url, banner_type, banner_season))
 
-        return images
+    def _get_xml_data(self, url, filter_func = None, zip_name = None, callback 
= None):
+        data = urllib.urlopen(url)
+        if zip_name:
+            zipfile = ZipFile(StringIO(data.read()))
+            data = zipfile.open(zip_name)
+        if not data:
+            raise Exception("Failed to get any data")
+
+        e = ExpatParseXml(callback, filter_func)
+        e.parse(data)
+        return e.xml
+
+
+class ExpatParseXml(object):
+    def __init__(self, callback, filter_func):
+        self.el_container = None
+        self.el_name = None
+        self.el_attr_name = None
+        self.el_attrs = None
+        self.el_callback = callback if callback else self.stash_xml
+        self.el_filter_func = filter_func # only used by stash_xml()
+        self.xml = {}
+
+        self.parser = expat.ParserCreate()
+        self.parser.StartElementHandler = self.start_element
+        self.parser.EndElementHandler = self.end_element
+        self.parser.CharacterDataHandler = self.char_data
+
+    def parse(self, fh):
+        # Sadly ParseFile(fh) actually mangles the data, so we parse the file 
line by line:
+        for line in fh:
+            self.parser.Parse(line)
+
+    def start_element(self, name, attrs):
+        if not self.el_name:
+            if not self.el_container:
+                self.el_container = name
+                self.el_callback(name, attrs)
+            else:
+                self.el_name = name
+                self.el_attrs = {}
+        elif not self.el_attr_name:
+            self.el_attr_name = name
+
+    def end_element(self, name):
+        if self.el_attr_name and name == self.el_attr_name:
+            self.el_attr_name = None
+        elif self.el_name and name == self.el_name:
+            self.el_callback(self.el_name, self.el_attrs)
+            self.el_name = None
+            self.el_attr_name = None
+
+    def char_data(self, data):
+        if self.el_attr_name:
+            if self.el_attr_name in self.el_attrs:
+                self.el_attrs[self.el_attr_name] += data
+            else:
+                self.el_attrs[self.el_attr_name] = data
+
+    def stash_xml(self, name, attrs):
+        if self.el_filter_func:
+            attrs = self.el_filter_func(name, attrs)
+            if attrs is None:
+                return
+        if name in self.xml:
+            self.xml[name].append(attrs)
+        else:
+            self.xml[name] = [ attrs ]
diff --git a/script.module.metahandler/resources/settings.xml 
b/script.module.metahandler/resources/settings.xml
index 23710cd..7110cc7 100644
--- a/script.module.metahandler/resources/settings.xml
+++ b/script.module.metahandler/resources/settings.xml
@@ -2,12 +2,14 @@
 <settings>
 <category label="Metahandler">
     <setting id="meta_folder_location" type="folder" label="Meta Data Save 
Location" default="special://profile/addon_data/script.module.metahandler/"/>
-       <setting id="use_remote_db" type="bool"         label="Use a remote 
MySQL DB" default="False"/>
+         <setting id="use_remote_db" type="bool"       label="Use a remote 
MySQL DB" default="False"/>
                <setting id="db_address" type="text"    label="    Address"     
enable="eq(-1,true)" default="" />
                <setting id="db_port"    type="integer" label="    Port"        
        enable="eq(-2,true)" default="" />
                <setting id="db_user"    type="text"    label="    Username"    
enable="eq(-3,true)" default="" />
                <setting id="db_pass"    type="text"    label="    Password"    
enable="eq(-4,true)" default="" option="hidden"/>
                <setting id="db_name"    type="text"    label="    Database"    
enable="eq(-5,true)" default="video_cache" />
+    <setting type="sep" />
+    <setting label="Reset Dababase" type="action" 
action="RunScript($CWD/lib/metahandler/rm_DB.py)"/>
 </category>
 <category label="TMDB (Movies)">
     <setting id="tmdb_language" label="Language" type="labelenum" 
default="English (en)" values="English (en)|Český (cs)|Dansk (da)|Deutsch 
(de)|ελληνικά (el)|Español (es)|Français (fr)|Magyar (hu)|Italiano 
(it)|Nederlands (nl)|Polski (pl)|Português (pt)|Pусский 
(ru)|Slovenščina (sk)|Svenska (sv)|Türkçe (tr)"/>
@@ -16,4 +18,7 @@
     <setting type="sep" />
     <setting id="year_lock" label="Year Lock" type="bool" default="false"/>
 </category>
+<category label="TVDB (Series, Mangas and tvshows)">
+    <setting id="tvdb_language" label="Language" type="labelenum" 
default="English (en)" values="English (en)|čeština (cs)|Dansk (da)|Deutsch 
(de)|ελληνικά (el)|Español (es)|Suomeksi (fi)|Français (fr)| 
עברית (he)|Hrvatski (hr)|Magyar (hu)|Italiano (it)|日本語 
(ja)|Nederlands (nl)|Norsk (no)|Polski (pl)|Português (pt)|Pусский 
(ru)|Slovenski (sl)|Svenska (sv)|Türkçe (tr)|中文 (zh)"/>
+</category>
 </settings>

-----------------------------------------------------------------------

Summary of changes:
 script.module.metahandler/addon.xml                |    4 +-
 script.module.metahandler/changelog.txt            |   10 +
 script.module.metahandler/lib/metahandler/TMDB.py  |   27 +-
 .../lib/metahandler/metahandlers.py                |  213 ++++++++-
 script.module.metahandler/lib/metahandler/rm_DB.py |   32 ++
 .../lib/metahandler/thetvdbapi.py                  |  489 +++++++++++---------
 script.module.metahandler/resources/settings.xml   |    7 +-
 7 files changed, 539 insertions(+), 243 deletions(-)
 create mode 100644 script.module.metahandler/lib/metahandler/rm_DB.py


hooks/post-receive
-- 
Scripts

------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons

Reply via email to