Play images and video from Synology PhotoStation server

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # -*- coding: utf-8 -*-
  2. """
  3. kodiswift.urls
  4. ---------------
  5. This module contains URLRule class for dealing with url patterns.
  6. :copyright: (c) 2012 by Jonathan Beluch
  7. :license: GPLv3, see LICENSE for more details.
  8. """
  9. from __future__ import absolute_import
  10. import re
  11. from urllib import urlencode, unquote_plus, quote_plus
  12. from kodiswift.common import pickle_dict, unpickle_dict
  13. __all__ = ['UrlRule', 'AmbiguousUrlException', 'NotFoundException']
  14. class AmbiguousUrlException(Exception):
  15. pass
  16. class NotFoundException(Exception):
  17. pass
  18. class UrlRule(object):
  19. """A class the encapsulates a URL
  20. """
  21. def __init__(self, url_rule, view_func, name, options):
  22. """Stores the various properties related to a routing URL rule.
  23. It also provides a few methods to create URLs from the rule or to
  24. match a given URL against a rule.
  25. Args:
  26. url_rule: The relative url pattern for the rule.
  27. It may include <var_name> to denote where dynamic variables
  28. should be matched.
  29. view_func: The function that should be bound to this rule.
  30. This should be an actual function object.
  31. name: The name of the url rule. This is used in the reverse
  32. process of creating urls for a given rule.
  33. options: A dict containing any default values for the url rule.
  34. Warnings:
  35. view_func: The function signature should match any variable
  36. names in the provided url_rule.
  37. """
  38. self._name = name
  39. self._url_rule = url_rule
  40. self._view_func = view_func
  41. self._options = options or {}
  42. self._keywords = re.findall(r'<(.+?)>', url_rule)
  43. # change <> to {} for use with str.format()
  44. self._url_format = self._url_rule.replace('<', '{').replace('>', '}')
  45. # Make a regex pattern for matching incoming URLs
  46. rule = self._url_rule
  47. if rule != '/':
  48. # Except for a path of '/', the trailing slash is optional.
  49. rule = self._url_rule.rstrip('/') + '/?'
  50. p = rule.replace('<', '(?P<').replace('>', '>[^/]+?)')
  51. try:
  52. self._regex = re.compile('^' + p + '$')
  53. except re.error:
  54. raise ValueError('There was a problem creating this URL rule. '
  55. 'Ensure you do not have any unpaired angle '
  56. 'brackets: "<" or ">"')
  57. def __eq__(self, other):
  58. if isinstance(other, UrlRule):
  59. return (
  60. (self._name, self._url_rule, self._view_func, self._options) ==
  61. (other._name, other._url_rule, other._view_func, other._options)
  62. )
  63. else:
  64. raise NotImplementedError
  65. def __ne__(self, other):
  66. return not self == other
  67. def match(self, path):
  68. """Attempts to match a url to the given path.
  69. If successful, a tuple is returned. The first item is the matched
  70. function and the second item is a dictionary containing items to be
  71. passed to the function parsed from the provided path.
  72. Args:
  73. path (str): The URL path.
  74. Raises:
  75. NotFoundException: If the provided path does not match this
  76. url rule.
  77. """
  78. m = self._regex.search(path)
  79. if not m:
  80. raise NotFoundException
  81. # urlunencode the values
  82. items = dict((key, unquote_plus(val))
  83. for key, val in m.groupdict().items())
  84. # unpickle any items if present
  85. items = unpickle_dict(items)
  86. # We need to update our dictionary with default values provided in
  87. # options if the keys don't already exist.
  88. [items.setdefault(key, val) for key, val in self._options.items()]
  89. return self._view_func, items
  90. def _make_path(self, items):
  91. """Returns a relative path for the given dictionary of items.
  92. Uses this url rule's url pattern and replaces instances of <var_name>
  93. with the appropriate value from the items dict.
  94. """
  95. for key, val in items.items():
  96. if not isinstance(val, basestring):
  97. raise TypeError('Value "%s" for key "%s" must be an instance'
  98. ' of basestring' % (val, key))
  99. items[key] = quote_plus(val)
  100. try:
  101. path = self._url_format.format(**items)
  102. except AttributeError:
  103. # Old version of python
  104. path = self._url_format
  105. for key, val in items.items():
  106. path = path.replace('{%s}' % key, val)
  107. return path
  108. def _make_qs(self, items):
  109. """Returns a query string for the given dictionary of items. All keys
  110. and values in the provided items will be urlencoded. If necessary, any
  111. python objects will be pickled before being urlencoded.
  112. """
  113. return urlencode(pickle_dict(items))
  114. def make_path_qs(self, items):
  115. """Returns a relative path complete with query string for the given
  116. dictionary of items.
  117. Any items with keys matching this rule's url pattern will be inserted
  118. into the path. Any remaining items will be appended as query string
  119. parameters.
  120. All items will be urlencoded. Any items which are not instances of
  121. basestring, or int/long will be pickled before being urlencoded.
  122. .. warning:: The pickling of items only works for key/value pairs which
  123. will be in the query string. This behavior should only be
  124. used for the simplest of python objects. It causes the
  125. URL to get very lengthy (and unreadable) and Kodi has a
  126. hard limit on URL length. See the caching section if you
  127. need to persist a large amount of data between requests.
  128. """
  129. # Convert any ints and longs to strings
  130. for key, val in items.items():
  131. if isinstance(val, (int, long)):
  132. items[key] = str(val)
  133. # First use our defaults passed when registering the rule
  134. url_items = dict((key, val) for key, val in self._options.items()
  135. if key in self._keywords)
  136. # Now update with any items explicitly passed to url_for
  137. url_items.update((key, val) for key, val in items.items()
  138. if key in self._keywords)
  139. # Create the path
  140. path = self._make_path(url_items)
  141. # Extra arguments get tacked on to the query string
  142. qs_items = dict((key, val) for key, val in items.items()
  143. if key not in self._keywords)
  144. qs = self._make_qs(qs_items)
  145. if qs:
  146. return '?'.join([path, qs])
  147. return path
  148. @property
  149. def regex(self):
  150. """The regex for matching paths against this url rule."""
  151. return self._regex
  152. @property
  153. def view_func(self):
  154. """The bound function"""
  155. return self._view_func
  156. @property
  157. def url_format(self):
  158. """The url pattern"""
  159. return self._url_format
  160. @property
  161. def name(self):
  162. """The name of this url rule."""
  163. return self._name
  164. @property
  165. def keywords(self):
  166. """The list of path keywords for this url rule."""
  167. return self._keywords