The branch, frodo has been updated
       via  2db85939212f142d4569c25fae8ed657d3dd1d95 (commit)
      from  b17af2758099b21227e287487ccbda4c1cb4c5e0 (commit)

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

commit 2db85939212f142d4569c25fae8ed657d3dd1d95
Author: ronie <[email protected]>
Date:   Sun Feb 24 23:56:16 2013 +0100

    [script.module.xbmcswift2] -v2.4.0

diff --git a/script.module.xbmcswift2/addon.xml 
b/script.module.xbmcswift2/addon.xml
index 5bff450..3d5d47c 100644
--- a/script.module.xbmcswift2/addon.xml
+++ b/script.module.xbmcswift2/addon.xml
@@ -1,4 +1,4 @@
-<addon id="script.module.xbmcswift2" name="xbmcswift2" provider-name="Jonathan 
Beluch (jbel)" version="2.3.2">
+<addon id="script.module.xbmcswift2" name="xbmcswift2" provider-name="Jonathan 
Beluch (jbel)" version="2.4.0">
   <requires>
     <import addon="xbmc.python" version="2.1.0" />
   </requires>
diff --git a/script.module.xbmcswift2/changelog.txt 
b/script.module.xbmcswift2/changelog.txt
index 1524744..a1a092c 100644
--- a/script.module.xbmcswift2/changelog.txt
+++ b/script.module.xbmcswift2/changelog.txt
@@ -1,6 +1,38 @@
 CHANGES
 =======
 
+
+Version 2.4.0
+-------------
+- Allow ints to be passed to plugin.url_for() and str() them.
+- plugin.set_resolved_url() now takes an item dict instead of a URL. (The URL
+  is still allowed for backwards compatibility but is decprecated).
+- Ability to replace the context menu items by setting 'replace_context_menu'
+  to True in an item dict.
+- pluign.get_setting() now requires an extra paramter, *converter*, which
+  converts the item from XML to a python type specified by converter.
+- Major bug fix to accomodate handles > 0 in XBMC Frodo.
+- Auto-call plugin.finish(succeeded=False) if a view returns None.
+- XBMC now decides the default player when using plugin.play_video().
+- Storages now call sync() when clear() is called.
+- Added plugin.clear_function_cache() which clears the storage behind the
+  @cached and @cached_route decorators.
+- Add ability to set stream info for list items. Also, devs can now add
+  stream_info to item dicts.
+- Added the actions module. Contains actions.background() and
+  actions.update_view() which are convenience wrappers for RunPlugin() and
+  Container.Update().
+- Allow passing of functions to url_for.
+- 'properties' key in an item dict should now be a dictionary. A list of tuples
+  is still allowed for backwards compatibility.
+- Addon user is now prompted and storage automatically cleared if it becomes
+  corrupted.
+- Added subtitles support. A 'subtitles' parameter was added to
+  plugin.set_resolved_url().
+- xbmcswift2 URLs and routing code is now indifferent to a trailing slash.
+- Bug fixes. See https://github.com/jbeluch/xbmcswift2/issues?milestone=3.
+
+
 Version 2.3.2
 -------------
 
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/__init__.py 
b/script.module.xbmcswift2/lib/xbmcswift2/__init__.py
index f7a9ca0..5a3634a 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/__init__.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/__init__.py
@@ -54,12 +54,12 @@ except ImportError:
     from logger import log
 
     # Mock the XBMC modules
-    from mockxbmc import xbmc, xbmcgui, xbmcplugin, xbmcaddon
+    from mockxbmc import xbmc, xbmcgui, xbmcplugin, xbmcaddon, xbmcvfs
     xbmc = module(xbmc)
     xbmcgui = module(xbmcgui)
     xbmcplugin = module(xbmcplugin)
     xbmcaddon = module(xbmcaddon)
-    xbmcvfs = module()
+    xbmcvfs = module(xbmcvfs)
 
 
 from xbmcswift2.storage import TimedStorage
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/listitem.py 
b/script.module.xbmcswift2/lib/xbmcswift2/listitem.py
index fd6db23..ffe7f97 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/listitem.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/listitem.py
@@ -108,6 +108,10 @@ class ListItem(object):
         '''Sets a property for the given key and value'''
         return self._listitem.setProperty(key, value)
 
+    def add_stream_info(self, stream_type, stream_values):
+        '''Adds stream details'''
+        return self._listitem.addStreamInfo(stream_type, stream_values)
+
     def get_icon(self):
         '''Returns the listitem's icon image'''
         return self._icon
@@ -181,7 +185,8 @@ class ListItem(object):
     @classmethod
     def from_dict(cls, label=None, label2=None, icon=None, thumbnail=None,
                   path=None, selected=None, info=None, properties=None,
-                  context_menu=None, is_playable=None, info_type='video'):
+                  context_menu=None, replace_context_menu=False,
+                  is_playable=None, info_type='video', stream_info=None):
         '''A ListItem constructor for setting a lot of properties not
         available in the regular __init__ method. Useful to collect all
         the properties in a dict and then use the **dct to call this
@@ -199,12 +204,18 @@ class ListItem(object):
             listitem.set_is_playable(True)
 
         if properties:
+            # Need to support existing tuples, but prefer to have a dict for
+            # properties.
+            if hasattr(properties, 'items'):
+                properties = properties.items()
             for key, val in properties:
                 listitem.set_property(key, val)
 
-        # By default doesn't replace, use .add_context_menu_items if you wish
-        # to set replace_items=True
+        if stream_info:
+            for stream_type, stream_values in stream_info.items():
+                listitem.add_stream_info(stream_type, stream_values)
+
         if context_menu:
-            listitem.add_context_menu_items(context_menu)
+            listitem.add_context_menu_items(context_menu, replace_context_menu)
 
         return listitem
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/plugin.py 
b/script.module.xbmcswift2/lib/xbmcswift2/plugin.py
index 6e66c7b..7b635aa6 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/plugin.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/plugin.py
@@ -96,6 +96,10 @@ class Plugin(XBMCMixin):
         # A flag to keep track of a call to xbmcplugin.endOfDirectory()
         self._end_of_directory = False
 
+        # Keep track of the update_listing flag passed to
+        # xbmcplugin.endOfDirectory()
+        self._update_listing = False
+
         # The plugin's named logger
         self._log = setup_log(self._addon_id)
 
@@ -272,11 +276,18 @@ class Plugin(XBMCMixin):
         Raises AmbiguousUrlException if there is more than one possible
         view for the given endpoint name.
         '''
-        if endpoint not in self._view_functions.keys():
-            raise NotFoundException, ('%s doesn\'t match any known patterns.' %
-                                      endpoint)
-
-        rule = self._view_functions[endpoint]
+        try:
+            rule = self._view_functions[endpoint]
+        except KeyError:
+            try:
+                rule = (rule for rule in self._view_functions.values() if 
rule.view_func == endpoint).next()
+            except StopIteration:
+                raise NotFoundException(
+                    '%s doesn\'t match any known patterns.' % endpoint)
+
+        # rule can be None since values of None are allowed in the
+        # _view_functions dict. This signifies more than one view function is
+        # tied to the same name.
         if not rule:
             # TODO: Make this a regular exception
             raise AmbiguousUrlException
@@ -294,14 +305,14 @@ class Plugin(XBMCMixin):
                      path, view_func.__name__)
             listitems = view_func(**items)
 
-            # XXX: The UI Container listing call to plugin or the resolving
-            #      url call always has a handle greater or equals 0. 
RunPlugin()
-            #      call using a handle -1. we only auto-call endOfDirectory 
for 
-            #      the UI Container listing call. and set_resolve_url() also
-            #      set the _end_of_directory flag so we do not call finish() 
for it.
-            # Allow the returning of bare dictionaries so we can cache view
+            # Only call self.finish() for UI container listing calls to plugin
+            # (handle will be >= 0). Do not call self.finish() when called via
+            # RunPlugin() (handle will be -1).
             if not self._end_of_directory and self.handle >= 0:
-                listitems = self.finish(listitems)
+                if listitems is None:
+                    self.finish(succeeded=False)
+                else:
+                    listitems = self.finish(listitems)
 
             return listitems
         raise NotFoundException, 'No matching view found for %s' % path
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/storage.py 
b/script.module.xbmcswift2/lib/xbmcswift2/storage.py
index 908a64f..92a2b11 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/storage.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/storage.py
@@ -143,6 +143,10 @@ class _Storage(collections.MutableMapping, 
_PersistentDictMixin):
 
     initial_update = collections.MutableMapping.update
 
+    def clear(self):
+        super(_Storage, self).clear()
+        self.sync()
+
 
 class TimedStorage(_Storage):
     '''A dict with the ability to persist to disk and TTL for items.'''
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/urls.py 
b/script.module.xbmcswift2/lib/xbmcswift2/urls.py
index ca19bdd..472d7a2 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/urls.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/urls.py
@@ -50,7 +50,11 @@ class UrlRule(object):
         self._url_format = self._url_rule.replace('<', '{').replace('>', '}')
 
         # Make a regex pattern for matching incoming URLs
-        p = self._url_rule.replace('<', '(?P<').replace('>', '>[^/]+?)')
+        rule = self._url_rule
+        if rule != '/':
+            # Except for a path of '/', the trailing slash is optional.
+            rule = self._url_rule.rstrip('/') + '/?'
+        p = rule.replace('<', '(?P<').replace('>', '>[^/]+?)')
 
         try:
             self._regex = re.compile('^' + p + '$')
@@ -130,7 +134,7 @@ class UrlRule(object):
         parameters.
 
         All items will be urlencoded. Any items which are not instances of
-        basestring will be pickled before being urlencoded.
+        basestring, or int/long will be pickled before being urlencoded.
 
         .. warning:: The pickling of items only works for key/value pairs which
                      will be in the query string. This behavior should only be
@@ -139,6 +143,11 @@ class UrlRule(object):
                      hard limit on URL length. See the caching section if you
                      need to persist a large amount of data between requests.
         '''
+        # Convert any ints and longs to strings
+        for key, val in items.items():
+            if isinstance(val, (int, long)):
+                items[key] = str(val)
+
         # First use our defaults passed when registering the rule
         url_items = dict((key, val) for key, val in self._options.items()
                          if key in self._keywords)
diff --git a/script.module.xbmcswift2/lib/xbmcswift2/xbmcmixin.py 
b/script.module.xbmcswift2/lib/xbmcswift2/xbmcmixin.py
index 9af96f6..f976c05 100644
--- a/script.module.xbmcswift2/lib/xbmcswift2/xbmcmixin.py
+++ b/script.module.xbmcswift2/lib/xbmcswift2/xbmcmixin.py
@@ -2,11 +2,12 @@ import os
 import sys
 import time
 import shelve
+import urllib
 from datetime import timedelta
 from functools import wraps
 
 import xbmcswift2
-from xbmcswift2 import xbmc, xbmcaddon, xbmcplugin
+from xbmcswift2 import xbmc, xbmcaddon, xbmcplugin, xbmcgui
 from xbmcswift2.storage import TimedStorage
 from xbmcswift2.logger import log
 from xbmcswift2.constants import VIEW_MODES, SortMethod
@@ -14,6 +15,8 @@ from common import Modes, DEBUG_MODES
 from request import Request
 
 
+
+
 class XBMCMixin(object):
     '''A mixin to add XBMC helper methods. In order to use this mixin,
     the child class must implement the following methods and
@@ -31,6 +34,8 @@ class XBMCMixin(object):
 
         _end_of_directory = False
 
+        _update_listing
+
         self.handle
 
     # optional
@@ -39,6 +44,9 @@ class XBMCMixin(object):
     _unsynced_storages = None
     # TODO: Ensure above is implemented
     '''
+
+    _function_cache_name = '.functions'
+
     def cached(self, TTL=60 * 24):
         '''A decorator that will cache the output of the wrapped function. The
         key used for the cache is the function name as well as the `*args` and
@@ -51,7 +59,7 @@ class XBMCMixin(object):
         '''
         def decorating_function(function):
             # TODO test this method
-            storage = self.get_storage('.functions', file_format='pickle',
+            storage = self.get_storage(self._function_cache_name, 
file_format='pickle',
                                        TTL=TTL)
             kwd_mark = 'f35c2d973e1bbbc61ca60fc6d7ae4eb3'
 
@@ -77,6 +85,13 @@ class XBMCMixin(object):
             return wrapper
         return decorating_function
 
+    def clear_function_cache(self):
+        '''Clears the storage that caches results when using
+        :meth:`xbmcswift2.Plugin.cached_route` or
+        :meth:`xbmcswift2.Plugin.cached`.
+        '''
+        self.get_storage(self._function_cache_name).clear()
+
     def list_storages(self):
         '''Returns a list of existing stores. The returned names can then be
         used to call get_storage().
@@ -119,7 +134,22 @@ class XBMCMixin(object):
         except KeyError:
             if TTL:
                 TTL = timedelta(minutes=TTL)
-            storage = TimedStorage(filename, file_format, TTL)
+
+            try:
+                storage = TimedStorage(filename, file_format, TTL)
+            except ValueError:
+                # Thrown when the storage file is corrupted and can't be read.
+                # Prompt user to delete storage.
+                choices = ['Clear storage', 'Cancel']
+                ret = xbmcgui.Dialog().select('A storage file is corrupted. It'
+                                              ' is recommended to clear it.',
+                                              choices)
+                if ret == 0:
+                    os.remove(filename)
+                    storage = TimedStorage(filename, file_format, TTL)
+                else:
+                    raise Exception('Corrupted storage file at %s' % filename)
+
             self._unsynced_storages[filename] = storage
             log.debug('Loaded storage "%s" from disk', name)
         return storage
@@ -131,7 +161,12 @@ class XBMCMixin(object):
         '''Returns the localized string from strings.xml for the given
         stringid.
         '''
-        return self.addon.getLocalizedString(stringid)
+        stringid = int(stringid)
+        if not hasattr(self, '_strings'):
+            self._strings = {}
+        if not stringid in self._strings:
+            self._strings[stringid] = self.addon.getLocalizedString(stringid)
+        return self._strings[stringid]
 
     def set_content(self, content):
         '''Sets the content type for the plugin.'''
@@ -143,10 +178,46 @@ class XBMCMixin(object):
         #assert content in contents, 'Content type "%s" is not valid' % content
         xbmcplugin.setContent(self.handle, content)
 
-    def get_setting(self, key):
+    def get_setting(self, key, converter=None, choices=None):
+        '''Returns the settings value for the provided key.
+        If converter is str, unicode, bool or int the settings value will be
+        returned converted to the provided type.
+        If choices is an instance of list or tuple its item at position of the
+        settings value be returned.
+        .. note:: It is suggested to always use unicode for text-settings
+                  because else xbmc returns utf-8 encoded strings.
+
+        :param key: The id of the setting defined in settings.xml.
+        :param converter: (Optional) Choices are str, unicode, bool and int.
+        :param converter: (Optional) Choices are instances of list or tuple.
+
+        Examples:
+            * ``plugin.get_setting('per_page', int)``
+            * ``plugin.get_setting('password', unicode)``
+            * ``plugin.get_setting('force_viewmode', bool)``
+            * ``plugin.get_setting('content', choices=('videos', 'movies'))``
+        '''
         #TODO: allow pickling of settings items?
         # TODO: STUB THIS OUT ON CLI
-        return self.addon.getSetting(id=key)
+        value = self.addon.getSetting(id=key)
+        if converter is str:
+            return value
+        elif converter is unicode:
+            return value.decode('utf-8')
+        elif converter is bool:
+            return value == 'true'
+        elif converter is int:
+            return int(value)
+        elif isinstance(choices, (list, tuple)):
+            return choices[int(value)]
+        elif converter is None:
+            log.warning('No converter provided, unicode should be used, '
+                        'but returning str value')
+            return value
+        else:
+            raise TypeError('Acceptable converters are str, unicode, bool and '
+                            'int. Acceptable choices are instances of list '
+                            ' or tuple.')
 
     def set_setting(self, key, val):
         # TODO: STUB THIS OUT ON CLI
@@ -228,19 +299,100 @@ class XBMCMixin(object):
         xbmc.executebuiltin('XBMC.Notification("%s", "%s", "%s", "%s")' %
                             (msg, title, delay, image))
 
-    def set_resolved_url(self, url):
-        item = xbmcswift2.ListItem(path=url)
-        item.set_played(True)
-        xbmcplugin.setResolvedUrl(self.handle, True, item.as_xbmc_listitem())
-        return [item]
+    def _listitemify(self, item):
+        '''Creates an xbmcswift2.ListItem if the provided value for item is a
+        dict. If item is already a valid xbmcswift2.ListItem, the item is
+        returned unmodified.
+        '''
+        info_type = self.info_type if hasattr(self, 'info_type') else 'video'
 
-    def play_video(self, item, player=xbmc.PLAYER_CORE_DVDPLAYER):
-        if not hasattr(item, 'as_xbmc_listitem'):
+        # Create ListItems for anything that is not already an instance of
+        # ListItem
+        if not hasattr(item, 'as_tuple'):
             if 'info_type' not in item.keys():
-                item['info_type'] = 'video'
+                item['info_type'] = info_type
             item = xbmcswift2.ListItem.from_dict(**item)
+        return item
+
+    def _add_subtitles(self, subtitles):
+        '''Adds subtitles to playing video.
+
+        :param subtitles: A URL to a remote subtitles file or a local filename
+                          for a subtitles file.
+
+        .. warning:: You must start playing a video before calling this method
+                     or it will loop for an indefinite length.
+        '''
+        # This method is named with an underscore to suggest that callers pass
+        # the subtitles argument to set_resolved_url instead of calling this
+        # method directly. This is to ensure a video is played before calling
+        # this method.
+        player = xbmc.Player()
+        for _ in xrange(30):
+            if player.isPlaying():
+                break
+            time.sleep(1)
+        else:
+            raise Exception('No video playing. Aborted after 30 seconds.')
+
+        player.setSubtitles(subtitles)
+
+    def set_resolved_url(self, item=None, subtitles=None):
+        '''Takes a url or a listitem to be played. Used in conjunction with a
+        playable list item with a path that calls back into your addon.
+
+        :param item: A playable list item or url. Pass None to alert XBMC of a
+                     failure to resolve the item.
+
+                     .. warning:: When using set_resolved_url you should ensure
+                                  the initial playable item (which calls back
+                                  into your addon) doesn't have a trailing
+                                  slash in the URL. Otherwise it won't work
+                                  reliably with XBMC's PlayMedia().
+        :param subtitles: A URL to a remote subtitles file or a local filename
+                          for a subtitles file to be played along with the
+                          item.
+        '''
+        if self._end_of_directory:
+            raise Exception('Current XBMC handle has been removed. Either '
+                            'set_resolved_url(), end_of_directory(), or '
+                            'finish() has already been called.')
+        self._end_of_directory = True
+
+        succeeded = True
+        if item is None:
+            # None item indicates the resolve url failed.
+            item = {}
+            succeeded = False
+
+        if isinstance(item, basestring):
+            # caller is passing a url instead of an item dict
+            item = {'path': item}
+
+        item = self._listitemify(item)
         item.set_played(True)
-        xbmc.Player(player).play(item.get_path(), item.as_xbmc_listitem())
+        xbmcplugin.setResolvedUrl(self.handle, succeeded,
+                                  item.as_xbmc_listitem())
+
+        # call to _add_subtitles must be after setResolvedUrl
+        if subtitles:
+            self._add_subtitles(subtitles)
+        return [item]
+
+    def play_video(self, item, player=None):
+        try:
+            # videos are always type video
+            item['info_type'] = 'video'
+        except TypeError:
+            pass  # not a dict
+
+        item = self._listitemify(item)
+        item.set_played(True)
+        if player:
+            _player = xbmc.Player(player)
+        else:
+            _player = xbmc.Player()
+        _player.play(item.get_path(), item.as_xbmc_listitem())
         return [item]
 
     def add_items(self, items):
@@ -254,19 +406,7 @@ class XBMCMixin(object):
                       :meth:`xbmcswift2.ListItem.from_dict` or an instance of
                       :class:`xbmcswift2.ListItem`.
         '''
-        # For each item if it is not already a list item, we need to create one
-        _items = []
-        info_type = self.info_type if hasattr(self, 'info_type') else 'video'
-
-        # Create ListItems for anything that is not already an instance of
-        # ListItem
-        for item in items:
-            if not isinstance(item, xbmcswift2.ListItem):
-                if 'info_type' not in item.keys():
-                    item['info_type'] = info_type
-                item = xbmcswift2.ListItem.from_dict(**item)
-            _items.append(item)
-
+        _items = [self._listitemify(item) for item in items]
         tuples = [item.as_tuple() for item in _items]
         xbmcplugin.addDirectoryItems(self.handle, tuples, len(tuples))
 
@@ -285,6 +425,7 @@ class XBMCMixin(object):
         Typically it is not necessary to call this method directly, as
         calling :meth:`~xbmcswift2.Plugin.finish` will call this method.
         '''
+        self._update_listing = update_listing
         if not self._end_of_directory:
             self._end_of_directory = True
             # Finalize the directory items
diff --git a/script.module.xbmcswift2/xbmcswift2_version 
b/script.module.xbmcswift2/xbmcswift2_version
index 4fd6b68..c062afb 100644
--- a/script.module.xbmcswift2/xbmcswift2_version
+++ b/script.module.xbmcswift2/xbmcswift2_version
@@ -1 +1 @@
-8fc7013db0baf89f258ae3ee158914390295ddc8
\ No newline at end of file
+0e7a3642499554edc8265fdf1ba6c5ee567daa78
\ No newline at end of file

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

Summary of changes:
 script.module.xbmcswift2/addon.xml                 |    2 +-
 script.module.xbmcswift2/changelog.txt             |   32 ++++
 .../lib/xbmcswift2/__init__.py                     |    4 +-
 script.module.xbmcswift2/lib/xbmcswift2/actions.py |   27 +++
 .../lib/xbmcswift2/listitem.py                     |   19 ++-
 script.module.xbmcswift2/lib/xbmcswift2/plugin.py  |   35 +++--
 script.module.xbmcswift2/lib/xbmcswift2/storage.py |    4 +
 script.module.xbmcswift2/lib/xbmcswift2/urls.py    |   13 +-
 .../lib/xbmcswift2/xbmcmixin.py                    |  197 +++++++++++++++++---
 script.module.xbmcswift2/xbmcswift2_version        |    2 +-
 10 files changed, 285 insertions(+), 50 deletions(-)
 create mode 100644 script.module.xbmcswift2/lib/xbmcswift2/actions.py


hooks/post-receive
-- 
Scripts

------------------------------------------------------------------------------
Everyone hates slow websites. So do we.
Make your web apps faster with AppDynamics
Download AppDynamics Lite for free today:
http://p.sf.net/sfu/appdyn_d2d_feb
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons

Reply via email to