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