The branch, eden has been updated
       via  6663b002ab184813c9800884f290c6ec00bdd272 (commit)
      from  bb04b4155c1dfcfb034008ad938d2be6e61902be (commit)

- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/plugins;a=commit;h=6663b002ab184813c9800884f290c6ec00bdd272

commit 6663b002ab184813c9800884f290c6ec00bdd272
Author: beenje <[email protected]>
Date:   Thu Mar 28 22:06:27 2013 +0100

    [plugin.video.synopsi] updated to version 0.5.2

diff --git a/plugin.video.synopsi/_src/install_xbmc_version.sh 
b/plugin.video.synopsi/_src/install_xbmc_version.sh
index dc6829b..113f309 100755
--- a/plugin.video.synopsi/_src/install_xbmc_version.sh
+++ b/plugin.video.synopsi/_src/install_xbmc_version.sh
@@ -1 +1,2 @@
-sudo aptitude install xbmc=2:11.0~git20120423.cd20772-1
+VERSION=$(aptitude versions "^xbmc$" | awk '{ print $2 }' | grep 2:11.0)
+sudo aptitude install xbmc=$VERSION
diff --git a/plugin.video.synopsi/_src/usefull_queries.sql 
b/plugin.video.synopsi/_src/usefull_queries.sql
index 3db2ceb..66771bc 100644
--- a/plugin.video.synopsi/_src/usefull_queries.sql
+++ b/plugin.video.synopsi/_src/usefull_queries.sql
@@ -6,7 +6,9 @@ select user_id, email, min(inserted) from api_apilog left join 
auth_user as AU o
 select user_id, email, min(inserted), date_joined from api_apilog left join 
auth_user as AU on AU.id=user_id group by user_id, date_joined, email order by 
min(date_joined) desc;
 
 /* api users with api request counts */
-select user_id, email, min(inserted), date_joined from api_apilog left join 
auth_user as AU on AU.id=user_id group by user_id, date_joined, email order by 
min(date_joined) desc;
 
 /* checkins whith software info filled-in      */
 select * from actions_checkin where data like '%software_info":"{%' and 
data!='{}' and data!='';
+
+/* fail responses percentil by method */
+select method, count(id), sum(case when response_status=0 then 1 else 0 end) 
as cnt_ok, sum(case when response_status=0 then 0 else 1 end) as cnt_fail, 
round(sum(case when response_status=0 then 0 else 1 end)::real/count(id)*100)  
as fail_percentil from api_apilog group by method order by fail_percentil;
diff --git a/plugin.video.synopsi/addon.xml b/plugin.video.synopsi/addon.xml
index 38fadf6..335a0f3 100644
--- a/plugin.video.synopsi/addon.xml
+++ b/plugin.video.synopsi/addon.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <addon id="plugin.video.synopsi"
        name="SynopsiTV"
-       version="0.5.1"
+       version="0.5.2"
        provider-name="Synopsi.TV">
        <requires>
                <import addon="xbmc.python" version="2.0"/>
diff --git a/plugin.video.synopsi/apiclient.py 
b/plugin.video.synopsi/apiclient.py
index 7f92119..ac60356 100644
--- a/plugin.video.synopsi/apiclient.py
+++ b/plugin.video.synopsi/apiclient.py
@@ -29,7 +29,7 @@ defaultIdentifyProps = commonTitleProps + ['tvshow_id']
 watchableTitleProps = commonTitleProps + ['watched']
 defaultTVShowProps = commonTitleProps + ['seasons']
 smallListProps = ['id', 'cover_medium', 'name', 'watched', 'type']
-defaultEpisodeProps = smallListProps + ['season_number', 'episode_number']
+defaultEpisodeProps = smallListProps + ['season_number', 'episode_number', 
'cover_large', 'tvshow_id', 'tvshow_name']
 allSeasonProps = ['id', 'cover_full', 'cover_large', 'cover_medium', 
'cover_small', 'cover_thumbnail', 'season_number', 'episodes_count', 
'watched_count']
 defaultSeasonProps = ['id', 'cover_medium', 'season_number', 'episodes_count', 
'watched_count']
 defaultSeasonProps2 = ['id', 'episodes']
@@ -462,7 +462,7 @@ class ApiClient(loggable.Loggable):
 
                return self.execute(req)
 
-       def unwatchedEpisodes(self, props=watchableTitleProps):
+       def unwatchedEpisodes(self, props=defaultEpisodeProps):
                req = {
                        'methodPath': 'profile/unwatched_episodes/',
                        'method': 'get',
diff --git a/plugin.video.synopsi/app_apiclient.py 
b/plugin.video.synopsi/app_apiclient.py
index aef8e50..5a1712e 100644
--- a/plugin.video.synopsi/app_apiclient.py
+++ b/plugin.video.synopsi/app_apiclient.py
@@ -160,13 +160,7 @@ class AppApiClient(ApiClient):
        def get_tvshow_season(self, season_id):
                season = self.season(season_id)
                
-               # fix names
-               for i in season['episodes']:
-                       episident = 'S%sE%s' % (i['season_number'], 
i['episode_number'])
-                       i['name'] = '%s - %s' % (episident, i['name'])
-                       top.stvList.updateTitle(i)
-
-               return season['episodes']
+               return season
 
        def get_title(self, stv_id, detailProps=defaultDetailProps, 
castProps=defaultCastProps):
                return self.title(stv_id, detailProps, castProps)
diff --git a/plugin.video.synopsi/cache.py b/plugin.video.synopsi/cache.py
index 81d56f8..ea8223f 100644
--- a/plugin.video.synopsi/cache.py
+++ b/plugin.video.synopsi/cache.py
@@ -1,5 +1,5 @@
 # xbmc
-import xbmc
+import xbmc, xbmcvfs
 
 # python standart lib
 import base64
@@ -47,7 +47,6 @@ class OfflineStvList(object):
                self.filePath = filePath or self.__class__.getDefaultFilePath()
                self.clear()
                self.uuid = uuid
-               #~ self.list()
 
        @classmethod
        def getDefaultFilePath(cls):
@@ -78,7 +77,6 @@ class OfflineStvList(object):
 
        def deserialize(self, _string):
                self.items, self.byType, self.byTypeId, self.byFilename, 
self.byStvId = pickle.loads(base64.b64decode(_string))
-               self.dump()
 
        def log(self, msg):
                log('CACHE / ' + msg)
@@ -130,9 +128,14 @@ class OfflineStvList(object):
 
                        if title.has_key('id'):
                                movie['stvId'] = title['id']
+
+                               # use synopsi runtime if possible
+                               if title.get('runtime'):
+                                       movie['runtime'] = 60 * title['runtime']
+                                       
                                self.log('identified: ' + title['name'])
                        else:
-                               self.log('File NOT identified %s' % 
movie['file'])
+                               self.log('NOT identified %s' % movie['file'])
 
                        # current block could raise ApiCallError, when there is 
not a real problem
                        try:
@@ -161,7 +164,7 @@ class OfflineStvList(object):
                        self.put(stv_title)
 
        def put(self, item):
-               " Put a new record in the list "
+               """ Put a new record in the list """
                self.log('PUT ' + dump(filtertitles(item)))
                # check if an item with this stvId is not already there
                if item.has_key('stvId') and self.hasStvId(item['stvId']):
@@ -311,10 +314,10 @@ class OfflineStvList(object):
 
        def clear(self):
                self.items = []
-               self.byType = { 'movie': {}, 'tvshow': {}, 'episode': {}, 
'season': {}}
-               self.byTypeId = {}
+               self.byType = { 'movie': {}, 'tvshow': {}, 'episode': {}, 
'season': {}}         # ids here are xbmc_ids, except tvshow_ids!
+               self.byTypeId = {}                                              
                                                                        # ids 
here are xbmc_ids
                self.byFilename = {}
-               self.byStvId = {}
+               self.byStvId = {}                                               
                                                                        # ids 
here are stv_ids
 
        def getItems(self):
                return self.items
@@ -403,12 +406,12 @@ class OfflineStvList(object):
 
        def save(self):
                self.log('SAVING / ' + self.filePath)
-               f = open(self.filePath, 'w')
+               f = xbmcvfs.File(self.filePath, 'w')
                f.write(self.serialize())
                f.close()
 
        def load(self):
-               f = open(self.filePath, 'r')
+               f = xbmcvfs.File(self.filePath, 'r')
                self.deserialize(f.read())
                f.close()
 
@@ -496,7 +499,7 @@ class OnlineStvList(OfflineStvList):
 class AppStvList(OnlineStvList):
        def get_local_tvshows(self):
                local_tvshows = self.getAllByType('tvshow')
-               log('local tvshows ' + dump(local_tvshows))
+                               
                return local_tvshows.values()
 
        def get_tvshow_local_seasons(self, stv_id):
diff --git a/plugin.video.synopsi/changelog.txt 
b/plugin.video.synopsi/changelog.txt
index d547882..002af98 100644
--- a/plugin.video.synopsi/changelog.txt
+++ b/plugin.video.synopsi/changelog.txt
@@ -1,3 +1,13 @@
+[B]Version 0.5.2[/B]
+
+* Fixed: if xbmc.Player doesn't provide runtime, plugin failed to show rating 
dialog
+* Fixed: api directors format change
++ Added: display year after movie name
++ Added: display episode identification in every episode listings
++ Added: breadcrumbs for listings
++ Added: display tvshow name in episode detail
+* Fixed: show correct titles on home screen (recently added)
+
 [B]Version 0.5.1 [/B]
 
 - Fixed: labels in movie details
diff --git a/plugin.video.synopsi/dialog.py b/plugin.video.synopsi/dialog.py
index 60959c1..1e49cc6 100644
--- a/plugin.video.synopsi/dialog.py
+++ b/plugin.video.synopsi/dialog.py
@@ -20,6 +20,9 @@ from cache import StvList, DuplicateStvIdException
 import top
 from threading import Thread
 
+# temporary
+import random
+
 ACTIONS_CLICK = [7, 100]
 LIST_ITEM_CONTROL_ID = 500
 HACK_GO_BACK = -2
@@ -111,23 +114,37 @@ class ListDialog(MyDialog):
                self.selectedMovie = None
                self.listControl = None
 
-       def onInit(self):
+
+       def onInit(self):               
+               win = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId())
+               
                self.listControl = self.getControl(LIST_ITEM_CONTROL_ID)
                self.listControl.reset()
-               
+
+               # asynchronous initialization
                if self.__dict__.get('_async_init'):
                        result = {}
                        kwargs = self._async_init.get('kwargs', {})
                        kwargs['result'] = result
                        try:
-                               self._async_init['method'](**kwargs)
+                               self._async_init['method'](**kwargs)    # 
method(result=result, +kwargs)
                        except (AuthenticationError, ListEmptyException) as e:
                                self.close()
                                return
                                
-                       self.data = result
+                       self.data.update(result['result'])
+
+               # exception of incoming data format
+               if self.data.has_key('episodes'):
+                       self.data['items'] = self.data['episodes']
+                       first_episode = self.data['items'][0]
+                       self.data['tvshow_name'] = first_episode['tvshow_name']
+                       self.data['_categoryName'] = self.data['tvshow_name'] + 
' - Season ' + first_episode['season_number']
                        
+               win.setProperty('ContainerCategory', 
self.data.get('_categoryName', ''))
+               
                self.updateItems()
+               
                                
        def updateItems(self):
                items = []
@@ -156,9 +173,27 @@ class ListDialog(MyDialog):
 
        def _getListItem(self, item):
                #~ itemPath = 'mode=' + str(ActionCode.VideoDialogShowById) + 
'&amp;stv_id=' + str(item['id'])
-               li = xbmcgui.ListItem(item['name'], 
iconImage=item['cover_medium'])
+               itemName = item['name']
+               
+               # add year after name
+               if item.has_key('year'):
+                       itemName += ' (' + str(item['year']) + ')'
+
+               # for episodes, add epis-ident
+               if item['type'] == 'episode':
+                       episident = get_episode_identifier(item)
+                       itemName = '%s - %s' % (episident, itemName)
+               
+               # create listitem with basic properties 
+               li = xbmcgui.ListItem(itemName, iconImage=item['cover_medium'])
                li.setProperty('id', str(item['id']))
                li.setProperty('type', str(item['type']))
+
+               if item['type'] == 'episode':
+                       li.setProperty('episode_number', 
str(item['episode_number']))
+                       li.setProperty('season_number', 
str(item['season_number']))
+                       li.setProperty('tvshow_name', str(item['tvshow_name']))
+                       
                #~ li.setProperty('path', str(itemPath))                
                        
                # prefer already set custom_overlay, if N/A set custom overlay
@@ -197,7 +232,14 @@ class ListDialog(MyDialog):
                        elif stv_id == HACK_SHOW_ALL_LOCAL_MOVIES:
                                show_submenu(ActionCode.LocalMovies)
                        else:
-                               show_video_dialog({'type': 
item.getProperty('type'), 'id': stv_id}, close=False)
+                               data = {'type': item.getProperty('type'), 'id': 
stv_id}
+                               if data['type'] == 'episode':
+                                       data['season_number'] = 
item.getProperty('season_number')
+                                       data['episode_number'] = 
item.getProperty('episode_number')
+                                       data['tvshow_name'] = 
item.getProperty('tvshow_name')
+                                       #~ data['tvshow_name'] = 
self.data['tvshow_name']
+
+                               show_video_dialog(data, close=False)
 
                
 
@@ -208,11 +250,10 @@ def show_movie_list(item_list):
        open_list_dialog({ 'items': item_list })
 
 def show_tvshows_episodes(stv_id):
-       def init_data(result, **kwargs):
-               log('asyn handler show_tvshows_episodes: ' + str(kwargs))
-               result['items'] = top.apiClient.get_tvshow_season(stv_id)
+       def init_data(result):
+               result['result'] = top.apiClient.get_tvshow_season(stv_id)
        
-       tpl_data = { '_async_init': { 'method': init_data, 'kwargs': {} }}
+       tpl_data = { '_async_init': { 'method': init_data }}
 
        open_list_dialog(tpl_data)
 
@@ -225,9 +266,10 @@ class VideoDialog(MyDialog):
                super(VideoDialog, self).__init__()
                self.data = kwargs['data']
                self.controlId = None
-               
+
        def _init_data(self):
                json_data = self.data
+
                if json_data.get('type') == 'tvshow':
                        stv_details = top.apiClient.tvshow(json_data['id'], 
cast_props=defaultCastProps)
                else:
@@ -280,7 +322,12 @@ class VideoDialog(MyDialog):
                
                # fill-in the form
                win = xbmcgui.Window(xbmcgui.getCurrentWindowDialogId())
-               win.setProperty("Movie.Title", self.data["name"] + 
'[COLOR=gray] (' + unicode(self.data.get('year')) + ')[/COLOR]')
+               str_title = self.data['name'] + '[COLOR=gray] (' + 
unicode(self.data.get('year')) + ')[/COLOR]'
+               if self.data['type'] == 'episode':
+                       episident = get_episode_identifier(self.data)           
        
+                       str_title = self.data['tvshow_name'] + ' - 
[COLOR=gray]' + episident + ' -[/COLOR] ' + str_title
+                       
+               win.setProperty("Movie.Title", str_title)
                win.setProperty("Movie.Plot", self.data["plot"])
                win.setProperty("Movie.Cover", self.data["cover_full"])
 
@@ -502,7 +549,7 @@ def video_dialog_template_fill(stv_details, json_data={}):
 
        append_tuple('Genre', 'genres', tr_genre)
        append_tuple('Cast', 'cast', tr_cast)
-       append_tuple('Director', 'directors', tr_genre)         # reuse 
tr_genre here   
+       append_tuple('Director', 'directors', tr_cast)          # reuse tr_cast 
here    
        append_tuple('Runtime', 'runtime', tr_runtime)
                
        if tpl_data.get('date'):
@@ -549,9 +596,9 @@ def get_submenu_item_list(action_code, **kwargs):
 
 def show_submenu(action_code, **kwargs):
        def init_data(result, **kwargs):
-               log('init_data kwargs: ' + str(kwargs))
-               result['items'] = get_submenu_item_list(**kwargs)
+               result['result'] = {'items': get_submenu_item_list(**kwargs)}
        
-       kwargs['action_code'] = action_code     
-       tpl_data = { '_async_init': { 'method': init_data, 'kwargs': kwargs }}
+       categoryName = submenu_categories_dict[action_code]
+       kwargs['action_code'] = action_code
+       tpl_data = { '_categoryName': categoryName, '_async_init': { 'method': 
init_data, 'kwargs': kwargs }}
        open_list_dialog(tpl_data)
diff --git a/plugin.video.synopsi/loggable.py b/plugin.video.synopsi/loggable.py
index a5ea521..17c9d7b 100644
--- a/plugin.video.synopsi/loggable.py
+++ b/plugin.video.synopsi/loggable.py
@@ -18,7 +18,7 @@ class Loggable(object):
                # assure that log dir exists
                logdir = self.get_log_dir()
 
-               if not os.path.exists(logdir):
+               if not xbmcvfs.exists(logdir):
                        xbmcvfs.mkdir(logdir)
 
                fh = logging.handlers.RotatingFileHandler(os.path.join(logdir, 
self.name + '.log'), mode='w', backupCount=2)
diff --git a/plugin.video.synopsi/resources/const.py 
b/plugin.video.synopsi/resources/const.py
index ceca21c..45e41d1 100644
--- a/plugin.video.synopsi/resources/const.py
+++ b/plugin.video.synopsi/resources/const.py
@@ -6,4 +6,6 @@ KEY="59c53964b1013defcff0155f6e4d54a4"
 SECRET="487615d20b22cdce510fd3476ed84d924e2b0c45ce7c49dc621764e05fae0904"
 
 # addon
-SERVICE_IFACE_VERSION=1
+# incrementing this will cause restarting plugin after update
+SERVICE_IFACE_VERSION=2
+
diff --git 
a/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml 
b/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
index a89b681..98a4627 100644
--- a/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
+++ b/plugin.video.synopsi/resources/skins/Default/720p/custom_MyVideoNav.xml
@@ -10,9 +10,34 @@
                <include>ContentPanelBackgrounds</include>
                <control type="group">
                        <include>Window_OpenClose_Animation</include>
-<!--           <include>CommonRootView</include> <!-- view id = 50 -->
-<!--           <include>FullWidthList</include> <!-- view id = 51 -->
-<!--           <include>ThumbnailView</include> <!-- view id = 500 -->
+                       
+                       <control type="image">
+                               <description>Section header image</description>
+                               <posx>20</posx>
+                               <posy>3</posy>
+                               <width>35</width>
+                               <height>35</height>
+                               <aspectratio>keep</aspectratio>
+                               <texture>icon_video.png</texture>
+                       </control>
+                       <control type="grouplist">
+                               <posx>65</posx>
+                               <posy>5</posy>
+                               <width>1000</width>
+                               <height>30</height>
+                               <orientation>horizontal</orientation>
+                               <align>left</align>
+                               <itemgap>5</itemgap>
+                               <control type="label">
+                                       <include>WindowTitleCommons</include>
+                                       <label>$LOCALIZE[3]</label>
+                               </control>
+                               <control type="label">
+                                       <include>WindowTitleCommons</include>
+                                       <label>[COLOR=blue] - 
[/COLOR]$INFO[Window.Property(ContainerCategory)]</label>
+                                       
<visible>!IsEmpty(Container.FolderName)</visible>
+                               </control>
+                       </control>
                        <control type="group">
                                <visible>Control.IsVisible(500)</visible>
                                <include>VisibleFadeEffect</include>
@@ -114,6 +139,7 @@
                                                        <align>center</align>
                                                        <aligny>center</aligny>
                                                        
<info>ListItem.Label</info>
+                                                       <scrollsuffix>       ~  
     </scrollsuffix>
                                                </control>
                                                <control type="image">
                                                        <posx>180</posx>
diff --git a/plugin.video.synopsi/scrobbler.py 
b/plugin.video.synopsi/scrobbler.py
index 62162f2..1cbdc39 100644
--- a/plugin.video.synopsi/scrobbler.py
+++ b/plugin.video.synopsi/scrobbler.py
@@ -90,10 +90,10 @@ class SynopsiPlayer(xbmc.Player):
                else:
                        if xbmc.Player().isPlayingVideo():
                                self.playing = True
-                               self.total_time = xbmc.Player().getTotalTime()
+                               self.media_file = self.getPlayingFile()
+                               self.total_time = self.getTotalTime()
                                self.current_time = self.get_time()
                                self.started()
-                               self.media_file = self.getPlayingFile()
                                self.mediainfotag = self.getVideoInfoTag()
                                self.last_played_file = self.media_file
                                self.subtitle_file = self.getSubtitles()
@@ -133,6 +133,15 @@ class SynopsiPlayer(xbmc.Player):
                        
                return t
 
+       def getTotalTime(self):
+               tt = xbmc.Player().getTotalTime()
+
+               if not tt:
+                       title = self.cache.getByFilename(self.media_file)
+                       tt = title.get('runtime')
+               
+               return tt
+
        def get_media_info_tag(self):
                try:
                        self.mediainfotag = self.getVideoInfoTag()
@@ -174,9 +183,14 @@ class SynopsiPlayerDecor(SynopsiPlayer):
 
        def stopped(self):
                self.playerEvent('stop')
+
+               # if we still dont have total runtime, use some default average 
(this should now be very rare case)
+               if not self.total_time:
+                       self.total_time = 5400
+               
                percent = self.current_time / self.total_time
                self.log('percent:' + str(self.current_time / self.total_time))
-
+               
                # ask for rating only if more than 70% of movie passed
                if percent > 0.7:
                        self.rate_file(self.last_played_file)
diff --git a/plugin.video.synopsi/tests/apiclient.unittest.py 
b/plugin.video.synopsi/tests/apiclient.unittest.py
index 5ad2284..49f1795 100644
--- a/plugin.video.synopsi/tests/apiclient.unittest.py
+++ b/plugin.video.synopsi/tests/apiclient.unittest.py
@@ -4,6 +4,8 @@ from unittest import *
 import logging
 import json
 from copy import copy
+import random
+import string
 
 # test helper
 from common import connection
@@ -92,10 +94,58 @@ class ApiTest(TestCase):
                stv_title = client.titleIdentify(**ident2)
                self.assertTrue(stv_title.has_key('type'))
 
+               ident3 = {
+                       'file_name': 
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+               }
+
+               stv_title = client.titleIdentify(**ident3)
+
+               self.assertTrue(stv_title.has_key('type'))
+
+               ident = {
+                       'file_name': 
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+                       'stv_title_hash': None,
+                       'os_title_hash': None
+               }
+
+               stv_title = client.titleIdentify(**ident)
+
+               self.assertTrue(stv_title.has_key('type'))
+               
+               ident = {
+                       'file_name': 
'_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
+                       'stv_title_hash': ''
+               }
+
+               stv_title = client.titleIdentify(**ident)
+
+               self.assertTrue(stv_title.has_key('type'))
+
+               ident = {
+                       'file_name': 'Avatar.avi',
+                       'stv_title_hash': ''
+               }
+
+               stv_title = client.titleIdentify(**ident)
+
+               self.assertTrue(stv_title.has_key('type'))
+
+               ident = {
+                       'file_name': 'Rambo.avi',
+               }
+
+               stv_title = client.titleIdentify(**ident)
+
+               self.assertTrue(stv_title.has_key('type'))
+
 
        def test_library_add(self):
                client.getAccessToken()
-
+               
+               # change the device id, to use empty library
+               randstr = ''.join(random.choice(string.ascii_uppercase + 
string.digits) for x in range(10))
+               client.device_id = 'testing_' + randstr
+               
                ident = {
                        'file_name': 
'/Volumes/FLOAT/Film/_videne/Notorious/Notorious.[2009self.Eng].TELESYNC.DivX-LTT.avi',
                        'stv_title_hash': 
'8b05ff1ad4865480e4705a42b413115db2bf94db',
@@ -142,21 +192,55 @@ class ApiTest(TestCase):
                self.assertTrue(data.has_key('titles'))
                self.assertTrue(len(data['titles']) > 0)
 
-       def test_profile_recco_local(self):
 
+       def test_profile_recco_local(self):
+               """ 
+                       To test local recco, we have to prepare a scenario for 
it:
+                       - create new client with origin library
+                       - get global recco
+                       - add some random titles from global recco to library
+                       - add titles not in global recco to library
+                       - test that first title is in local recco, and second 
not
+               """
+               props = [ 'id', 'name', 'year', 'cover_small' ]
+
+               device_id = ''.join([random.choice(string.hexdigits) for n in 
xrange(32)])
+               new_client = ApiClient(c['base_url'], c['key'], c['secret'], 
c['username'], c['password'], device_id, debugLvl = logging.DEBUG, 
rel_api_url=c['rel_api_url'])
+               
+               # get global recco
+               reco_global = new_client.profileRecco('movie', False, 50, props)
+               reco_global_ids = [i['id'] for i in reco_global['titles']]
+               
+               # pick five titles
+               random_pick = list(set([random.choice(reco_global_ids) for i in 
xrange(0,5)]))
+               
+               # add them to library
+               for i in random_pick:
+                       new_client.libraryTitleAdd(i)
+               
+               # wait a little !
+               time.sleep(1)
+               
+               # get local recco
+               reco_local = new_client.profileRecco('movie', True, 50, props)
 
-               props = [ 'year', 'cover_small' ]
-               data = client.profileRecco('movie', True, 5, props)
-
-               self.assertTrue(data.has_key('recco_id'))
-               self.assertTrue(data.has_key('titles'))
-               self.assertTrue(len(data['titles']) > 0)
+               reco_local_ids = [i['id'] for i in reco_local['titles']]
+               
+               # check local recco             
+               self.assertTrue(reco_local.has_key('recco_id'))
+               self.assertTrue(reco_local.has_key('titles'))
+               self.assertTrue(len(reco_local['titles']) > 0)
+               
+               # every picked movie should be in local recco
+               for i in random_pick:
+                       self.assertTrue(i in reco_local_ids)
+               
 
        def test_profile_recco_watched(self):
                props = [ 'id', 'year', 'cover_small' ]
                data = client.profileRecco('movie', False, 5, props)
                all_ids = [ i['id'] for i in data['titles'] ]
-               print dump(all_ids)
+
                self.assertTrue(data.has_key('recco_id'))
                self.assertTrue(data.has_key('titles'))
                self.assertTrue(len(data['titles']) > 0)
@@ -166,7 +250,7 @@ class ApiTest(TestCase):
 
                new_data = client.profileRecco('movie', False, 5, props)
                all_ids = [ i['id'] for i in new_data['titles'] ]
-               print dump(all_ids)
+
                self.assertFalse(check_id in all_ids)
 
 
@@ -196,12 +280,14 @@ class ApiTest(TestCase):
        def test_season(self):
                title = client.season(14376)
 
-               # print dump(title)
+               self.assertTrue(title.has_key('episodes'))
+               es = title['episodes']
+               self.assertTrue(es[0].has_key('season_number'))
+               self.assertTrue(es[0].has_key('episode_number'))
+               self.assertTrue(es[0].has_key('watched'))
+               self.assertTrue(es[0].has_key('id'))
+
 
-               self.assertTrue(title.has_key('cover_full'))
-               self.assertTrue(title.get('type')=='tvshow')
-               self.assertTrue(title.get('year')==2005)
-               self.assertTrue(title['cast'][0]['name']=='Josh Radnor')
 
        def test_unicode_input(self):
                data = {
@@ -216,8 +302,12 @@ class ApiTest(TestCase):
 
        def test_search(self):
                result = client.search('Adams aebler', 13)
-               # print dump(result)
+               
                self.assertTrue(result.has_key('search_result'))
+               self.assertTrue(result['search_result'][0]['id'] == 514461)
+
+
+               result = client.search('Love', 13)
                self.assertTrue(len(result['search_result']) == 13)
 
        def test_identify_correct(self):
@@ -225,6 +315,7 @@ class ApiTest(TestCase):
                # print dump(result)
                self.assertTrue(result['status']=='ok')
 
+       @skip('this needs deeper work')
        def test_identify_correct_library(self):
                TITLE_CORRECTION_TARGET = 1947362
                CORRECTION_FILE_HASH = 
'52b6f00222cdb3631d9914aee6b662961e924aa5'       # hash of my "three times" file
@@ -298,7 +389,7 @@ class ApiTest(TestCase):
                self.assertTrue(TITLE_CORRECTION_TARGET not in lib_ids)
                self.assertTrue(SOME_ID_IN_LIBRARY in lib_ids)
 
-
+       @skip('this needs deeper work')
        def test_correction_recco(self):
                TITLE_CORRECTION_TARGET = 1947362
                CORRECTION_FILE_HASH = 
'52b6f00222cdb3631d9914aee6b662961e924aa5'       # hash of my "three times" file
@@ -327,15 +418,25 @@ class ApiTest(TestCase):
                        print 'removing %d from library' % 
TITLE_CORRECTION_TARGET
                        client.libraryTitleRemove(TITLE_CORRECTION_TARGET)
 
-
-
+    
        def test_library(self):
                result = client.library(['date', 'genres', 'cover_small'])
-               print dump(result)
+               self.assertTrue(result.get('created'))
+               self.assertTrue(result.get('device_id'))
+               self.assertTrue(result.get('name'))
+               self.assertTrue(result.get('titles'))
+               self.assertTrue(type(result['titles']) is list)
+               
+               result2 = client.library(['id', 'cover_full', 'cover_large', 
'cover_medium', 'cover_small', 'cover_thumbnail', 'date', 'genres', 'url', 
'name', 'plot', 'released', 'trailer', 'type', 'year', 'runtime', 'directors', 
'writers', 'cast', 'watched'])
+               self.assertTrue(result2.get('created'))
+               self.assertTrue(result2.get('device_id'))
+               self.assertTrue(result2.get('name'))
+               self.assertTrue(result2.get('titles'))
+               self.assertTrue(type(result2['titles']) is list)
 
 if __name__ == '__main__':
        c = connection
-       client = ApiClient(c['base_url'], c['key'], c['secret'], c['username'], 
c['password'], c['device_id'], debugLvl = logging.WARNING, 
rel_api_url=c['rel_api_url'])
+       client = ApiClient(c['base_url'], c['key'], c['secret'], c['username'], 
c['password'], c['device_id'], debugLvl = logging.DEBUG, 
rel_api_url=c['rel_api_url'])
 
        logger = logging.getLogger()
 
diff --git a/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py 
b/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
index 5724774..c9803ba 100644
--- a/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
+++ b/plugin.video.synopsi/tests/fakeenv/xbmcvfs.py
@@ -1,3 +1,7 @@
 import os
 
 mkdir = os.mkdir
+
+exists = os.path.exists
+
+File = open
diff --git a/plugin.video.synopsi/tests/utilities.unittest.py 
b/plugin.video.synopsi/tests/utilities.unittest.py
index 5430fd0..025bc8c 100644
--- a/plugin.video.synopsi/tests/utilities.unittest.py
+++ b/plugin.video.synopsi/tests/utilities.unittest.py
@@ -22,13 +22,32 @@ class UtilitiesTest(TestCase):
                        movie['os_title_hash'] = hash_opensubtitle(path)
                        return movie
                
-               print gethash(hash_path)
+               #~ print gethash(hash_path)
+                               
+               nonex_hash = gethash('x://non-existent')
+               self.assertEqual(nonex_hash['stv_title_hash'], None)
+               self.assertEqual(nonex_hash['os_title_hash'], None)
+               
+               
+       @skip('this is not supposed to work yet')       
+       def test_samba_hash(self):      
+               # test samba share hash (assumes local samba share)
+               # cannot run this test outside xbmc
+               local_path = 
'~/Videos/Movies/Intouchables/Intouchables.2011.FRENCH.720p.BluRay.x264.mkv'
+               local_hash = gethash(local_path)
+       
+               samba_path = 
'smb://crux/movies/Intouchables/Intouchables.2011.FRENCH.720p.BluRay.x264.mkv'
+               samba_hash = gethash(local_path)
+               
+               self.assertEqual(local_hash, samba_hash)
+               
        
        def test_xml_sources(self):
                sources = get_movie_sources()
                sources.sort(key=len, reverse=True)
                print sources
-               
+
+       
        def test_rel_path(self):
                path1 = 
'/home/smid/Videos/_testset/TVShows/xxx/the/movie/file.avi'
                rel1 = rel_path(path1)
diff --git a/plugin.video.synopsi/utilities.py 
b/plugin.video.synopsi/utilities.py
index b918ad2..0bc0bcd 100644
--- a/plugin.video.synopsi/utilities.py
+++ b/plugin.video.synopsi/utilities.py
@@ -1,5 +1,5 @@
 # xbmc
-import xbmc, xbmcgui, xbmcaddon, xbmcplugin
+import xbmc, xbmcgui, xbmcaddon, xbmcplugin, xbmcvfs
 import CommonFunctions
 
 # python standart lib
@@ -45,7 +45,7 @@ SEARCH_RESULT_LIMIT = 15
 
 
 # api request title properties
-reccoDefaultProps = ['id', 'cover_medium', 'name', 'type', 'watched']
+reccoDefaultProps = ['id', 'cover_medium', 'name', 'type', 'watched', 'year']
 defaultDetailProps = ['id', 'cover_full', 'cover_large', 'cover_medium', 
'cover_small', 'cover_thumbnail', 'date', 'genres', 'url', 'name', 'plot', 
'released', 'trailer', 'type', 'year', 'directors', 'writers', 'runtime', 
'cast']
 tvshowdefaultDetailProps = defaultDetailProps + ['seasons']
 defaultCastProps = ['name']
@@ -82,13 +82,28 @@ class ActionCode:
        VideoDialogShow = 900
        VideoDialogShowById = 910
 
+submenu_categories = [
+       (ActionCode.MovieRecco, "Movie Recommendations"),
+       (ActionCode.TVShows, "Popular TV Shows"),
+       (ActionCode.LocalMovieRecco, "Local Movie Recommendations"),
+       (ActionCode.LocalTVShows, "Local TV Shows"),
+       (ActionCode.UnwatchedEpisodes, "Unwatched TV Show Episodes"),
+       (ActionCode.UpcomingEpisodes, "Upcoming TV Episodes"),
+       (ActionCode.LoginAndSettings, "Login and Settings")
+]
+
+submenu_categories_dict = dict(submenu_categories)
+
+# we do not want the all local movies have listed in main menu, so this is an 
easy fix
+submenu_categories_dict[ActionCode.LocalMovies] = 'All Your Local Movies'
+
 # texts
 t_noupcoming = 'There are no upcoming episodes from your tracked TV shows.'
 t_nounwatched = 'There are no unwatched episodes in your TV Show tracking'
 t_nolocalrecco = 'There are no items in this list. Either you have no movies 
in your library or they have not been recognized by Synopsi'
 t_nolocaltvshow = 'There are no items in this list. Either you have no 
episodes in your library or they have not been recognized by Synopsi'
 t_needrestart = 'To start the SynopsiTV service, please turn off your media 
center then turn it back on again. Do this now?'
-t_needrestart_update = 'Addon service has been updated. For the plugin to work 
correctly, turn off your media center then turn it back on again. Do this now?'
+t_needrestart_update = 'Addon has been recently updated. For the plugin to 
work, it\'s neccessary to restart your media center. Restart now ?'
 t_enter_title_to_search =  'Enter a title name to search for.'
 t_correct_search_title = 'Search for the correct title'
 
@@ -219,7 +234,7 @@ def check_first_run():
                                xbmc.executebuiltin('ReloadSkin()')             
                        raise Exception('Addon service is not running')
 
-def dialog_text(msg, max_line_length=20, max_lines=3):
+def dialog_text(msg, max_line_length=50, max_lines=3):
        line_end = [0]
        idx = -1
        line_no = 0
@@ -259,19 +274,19 @@ def list_get(alist, index, default=''):
                return default
 
 def dialog_ok(msg):
-       lines = dialog_text(msg, 45)
+       lines = dialog_text(msg)
        return xbmcgui.Dialog().ok(t_stv, list_get(lines, 0), list_get(lines, 
1), list_get(lines, 2))
 
 def dialog_yesno(msg):
-       lines = dialog_text(msg, 45)
+       lines = dialog_text(msg)
        return xbmcgui.Dialog().yesno(t_stv, list_get(lines, 0), 
list_get(lines, 1), list_get(lines, 2))
 
 
 def clear_setting_cache():
        "Clear cached addon setting. Useful after update"
        settingsPath = os.path.join(__profile__, 'settings.xml')
-       if os.path.exists(settingsPath):
-               os.remove(settingsPath)
+       if xbmcvfs.exists(settingsPath):
+               xbmcvfs.delete(settingsPath)
 
 def setting_cache_append_string(string):
        settingsPath = os.path.join(__profile__, 'settings.xml')
@@ -307,7 +322,7 @@ class XMLRatingDialog(xbmcgui.WindowXMLDialog):
 
        def onClick(self, controlId):
                """
-               For controlID see: <control id="11" type="button"> in 
SynopsiDialog.xml
+               For controlID see: <control id="11" type="button"> in Rating.xml
                """
                if controlId == 11:
                        self.response = 1
@@ -345,7 +360,7 @@ class XMLLoginDialog(xbmcgui.WindowXMLDialog):
 
        def onClick(self, controlId):
                """
-               For controlID see: <control id="11" type="button"> in 
SynopsiDialog.xml
+               For controlID see: <control id="11" type="button"> in Rating.xml
                """
                # log(str('onClick:'+str(controlId)))
 
@@ -396,19 +411,40 @@ def is_protected(path):
        return False
 
 
+def textfilter(bytestring):
+       import string,re
+
+       norm = string.maketrans('', '') #builds list of all characters
+       non_alnum = string.translate(norm, norm, string.letters+string.digits) 
+       
+       trans_nontext=string.maketrans(non_alnum,'?'*len(non_alnum))
+       cleaned=string.translate(bytestring, trans_nontext)
+       
+       return cleaned
+
 def stv_hash(filepath):
        """
        New synopsi hash. Hashing the sedond 512 kB of a file using SHA1.
        """
 
+       chunk_offset = 524288
+       chunk_length = 524288
+       
        sha1 = hashlib.sha1()
 
        try:
-               with open(filepath, 'rb') as f:
-                       f.seek(524288, 0)
-                       sha1.update(f.read(524288))
+               f = xbmcvfs.File(filepath, 'r')
+               f.seek(chunk_offset, 0)
+               fcontent = f.read(chunk_length)
+               if len(fcontent) != chunk_length:
+                       raise IOError()
+                       
+               sha1.update(fcontent)
+               f.close()
+       
        except (IOError) as e:
-               raise HashError('Unable to hash file [%s]' % filepath)
+               log('Unable to hash file [%s]' % filepath)
+               return None
 
        return sha1.hexdigest()
 
@@ -421,10 +457,11 @@ def old_stv_hash(filepath):
        sha1 = hashlib.sha1()
 
        try:
-               with open(filepath, 'rb') as f:
-                       sha1.update(f.read(256))
-                       f.seek(-256, 2)
-                       sha1.update(f.read(256))
+               f = xbmcvfs.File(filepath, 'rb')
+               sha1.update(f.read(256))
+               f.seek(-256, 2)
+               sha1.update(f.read(256))
+               f.close()
        except (IOError) as e:
                return None
 
@@ -439,9 +476,9 @@ def hash_opensubtitle(name):
                longlongformat = 'q'  # long long
                bytesize = struct.calcsize(longlongformat)
 
-               _file = open(name, "rb")
+               _file = xbmcvfs.File(name, 'rb')
 
-               filesize = os.path.getsize(name)
+               filesize = _file.size()
                hash = filesize
 
                if filesize < 65536 * 2:
@@ -467,7 +504,8 @@ def hash_opensubtitle(name):
                return returnedhash
 
        except(IOError):
-               raise HashError('Unable to hash file [%s]' % name)
+               log('Unable to hash file [%s]' % name)
+               return None
 
 
 def generate_deviceid():
@@ -522,14 +560,17 @@ def get_api_port():
        If nothing is changed return default 9090.
        """
 
-       path = os.path.join('special://profile', 'advancedsettings.xml')
-       path = xbmc.translatePath(path)
-
        try:
-               tree = et.parse(path)
-               root = tree.getroot()
+               path = os.path.join('special://profile', 'advancedsettings.xml')
+                               
+               f = xbmcvfs.File(path, 'r')
+               fcontent = f.read()
+               f.close()
+
+               root = et.fromstring(fcontent)
                nodes = root.findall('.//tcpport')
                value = int(nodes[0].text)
+
        except:
                value = 9090
 
@@ -579,15 +620,14 @@ def home_screen_fill(apiClient, cache):
 
                        # recco could return less than 5 items
                        if i < len(episode_recco):
-                               e = episode_recco[i]
-                               lib_item = cache.getByStvId(e['id'])
-                               log('episode %d %s' % (i, e['name']))
-                               log('lib_item %s' % (str(lib_item)))
-                               
WINDOW.setProperty("LatestEpisode.{0}.EpisodeTitle".format(i+1), e['name'])
-                               
WINDOW.setProperty("LatestEpisode.{0}.ShowTitle".format(i+1), e['name'])
-                               
WINDOW.setProperty("LatestEpisode.{0}.EpisodeNo".format(i+1), str(i))
-                               if lib_item:
-                                       
WINDOW.setProperty("LatestEpisode.{0}.Path".format(i+1), e['cover_large'])
+                               e = episode_recco[i]                            
+                               c_episode = cache.getByStvId(e['id'])
+                                       
+                                                               
+                               
WINDOW.setProperty("LatestEpisode.{0}.EpisodeTitle".format(i+1), 
e['tvshow_name'])                              # tv show name
+                               
WINDOW.setProperty("LatestEpisode.{0}.ShowTitle".format(i+1), e['name'])        
                                        # episode name
+                               
WINDOW.setProperty("LatestEpisode.{0}.EpisodeNo".format(i+1), 
get_episode_identifier(e))                # episode id string
+                               
WINDOW.setProperty("LatestEpisode.{0}.Path".format(i+1), c_episode['file'] if 
c_episode else '')
                                
WINDOW.setProperty("LatestEpisode.{0}.Thumb".format(i+1), e['cover_large'])
 
 
@@ -639,7 +679,7 @@ def get_rating():
        Get rating from user:
        1 = Amazing, 2 = OK, 3 = Terrible, 4 = Not rated
        """
-       ui = XMLRatingDialog("SynopsiDialog.xml", __addonpath__, "Default")
+       ui = XMLRatingDialog("Rating.xml", __addonpath__, "Default")
        ui.doModal()
        _response = ui.response
        del ui
@@ -691,26 +731,25 @@ def add_movie(movie, mode, iconimage):
        new_li = (u, li, isFolder)
 
        return new_li
-
-
+       
+       
 def show_categories():
        """
        Shows initial categories on home screen.
        """
        xbmc.executebuiltin("Container.SetViewMode(503)")
-       add_directory("Movie Recommendations", "url", ActionCode.MovieRecco, 
"list.png")
-       add_directory("Popular TV Shows", "url", ActionCode.TVShows, "list.png")
-       add_directory("Local Movie Recommendations", "url", 
ActionCode.LocalMovieRecco, "list.png")
-       add_directory("Local TV Shows", "url", ActionCode.LocalTVShows, 
"list.png")
-       add_directory("Unwatched TV Show Episodes", "url", 
ActionCode.UnwatchedEpisodes, "list.png")
-       add_directory("Upcoming TV Episodes", "url", 
ActionCode.UpcomingEpisodes, "list.png")
-       add_directory("Login and Settings", "url", ActionCode.LoginAndSettings, 
"list.png")
+       for categoryCode, categoryName in submenu_categories:
+               add_directory(categoryName, "url", categoryCode, "list.png")
 
 def get_movie_sources():               
        userdata = xbmc.translatePath('special://userdata')
        sourceFilePath = os.path.join(userdata, 'sources.xml')
-       tree = et.parse(sourceFilePath)
-       root = tree.getroot()
+       
+       f = xbmcvfs.File(sourceFilePath, 'r')
+       fcontent = f.read()
+       f.close()
+
+       root = et.fromstring(fcontent)
        el = root.findall('video/source/path')
        return sorted([i.text for i in el], key=len, reverse=True)
 
@@ -722,4 +761,5 @@ def rel_path(realpath):
        
        return realpath
                
-       
+def get_episode_identifier(item):
+       return 'S%sE%s' % (item.get('season_number', '??'), 
item.get('episode_number', '??'))
diff --git a/plugin.video.synopsi/xbmcrpc.py b/plugin.video.synopsi/xbmcrpc.py
index 19c4075..2b7fd81 100644
--- a/plugin.video.synopsi/xbmcrpc.py
+++ b/plugin.video.synopsi/xbmcrpc.py
@@ -8,7 +8,7 @@ import json
 from utilities import *
 from loggable import Loggable
 
-defaultProperties = ['file', 'imdbnumber', "lastplayed", "playcount"]
+defaultProperties = ['file', 'imdbnumber', "lastplayed", "playcount", 
"runtime"]
 
 class xbmcRPCclient(Loggable):
 

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

Summary of changes:
 plugin.video.synopsi/_src/install_xbmc_version.sh  |    3 +-
 plugin.video.synopsi/_src/usefull_queries.sql      |    4 +-
 plugin.video.synopsi/addon.xml                     |    2 +-
 plugin.video.synopsi/apiclient.py                  |    4 +-
 plugin.video.synopsi/app_apiclient.py              |    8 +-
 plugin.video.synopsi/cache.py                      |   25 +-
 plugin.video.synopsi/changelog.txt                 |   10 +
 plugin.video.synopsi/dialog.py                     |   81 +-
 plugin.video.synopsi/loggable.py                   |    2 +-
 plugin.video.synopsi/resources/const.py            |    4 +-
 .../Default/720p/{SynopsiDialog.xml => Rating.xml} |    0
 .../resources/skins/Default/720p/ViewsFileMode.xml |  613 --------
 .../skins/Default/720p/ViewsVideoLibrary.xml       | 1545 --------------------
 .../skins/Default/720p/customLoginDialog.xml       |  155 --
 .../skins/Default/720p/custom_MyVideoNav.xml       |   32 +-
 .../resources/skins/Default/720p/includes.xml      |    3 -
 plugin.video.synopsi/scrobbler.py                  |   20 +-
 plugin.video.synopsi/tests/apiclient.unittest.py   |  143 ++-
 plugin.video.synopsi/tests/fakeenv/xbmcvfs.py      |    4 +
 plugin.video.synopsi/tests/utilities.unittest.py   |   23 +-
 plugin.video.synopsi/utilities.py                  |  136 ++-
 plugin.video.synopsi/xbmcrpc.py                    |    2 +-
 22 files changed, 383 insertions(+), 2436 deletions(-)
 rename plugin.video.synopsi/resources/skins/Default/720p/{SynopsiDialog.xml => 
Rating.xml} (100%)
 delete mode 100644 
plugin.video.synopsi/resources/skins/Default/720p/ViewsFileMode.xml
 delete mode 100644 
plugin.video.synopsi/resources/skins/Default/720p/ViewsVideoLibrary.xml
 delete mode 100644 
plugin.video.synopsi/resources/skins/Default/720p/customLoginDialog.xml
 delete mode 100644 
plugin.video.synopsi/resources/skins/Default/720p/includes.xml


hooks/post-receive
-- 
Plugins

------------------------------------------------------------------------------
Own the Future-Intel(R) Level Up Game Demo Contest 2013
Rise to greatness in Intel's independent game demo contest. Compete 
for recognition, cash, and the chance to get your game on Steam. 
$5K grand prize plus 10 genre and skill prizes. Submit your demo 
by 6/6/13. http://altfarm.mediaplex.com/ad/ck/12124-176961-30367-2
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons

Reply via email to