Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit bbfb990f626b3c349d268aadc71327759c6c12ed @jbeluch committed Jun 29, 2011
@@ -0,0 +1,6 @@
+notes
+tmux.sh
+*.pyo
+*.pyc
+*.swp
+*.swo
No changes.
@@ -0,0 +1,34 @@
+import os
+from setuptools import setup, find_packages
+
+# Utility function to read the README file.
+# Used for the long_description. It's nice, because now 1) we have a top level
+# README file and 2) it's easier to type in the README file than to put a raw
+# string in below ...
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+ name = 'XBMC Swift',
+ version = '0.1',
+ author = 'Jonathan Beluc',
+ author_email = 'xbmc@jonathanbeluch.com',
+ description = 'A micro framework for rapid development of XBMC plugins.',
+ license = "GPL3",
+ keywords = "example documentation tutorial",
+ #url = '',
+ packages=find_packages(),
+ long_description=read('README.md'),
+ classifiers=[
+ "Development Status :: 3 - Alpha",
+ "Topic :: Utilities",
+ #"License :: OSI Approved :: BSD License",
+ 'License :: OSI Approved :: GNU Affero General Public License v3',
+ 'Programming Language :: Python',
+ ],
+ entry_points={
+ 'console_scripts': [
+ 'xbmcswift = xbmcswift.console:main',
+ ]
+ },
+)
@@ -0,0 +1,6 @@
+from .common import (urlparse, pickle_dict, unpickle_dict, clean_dict,
+ download_page, parse_qs, parse_url_qs, unhex)
+from .plugin import Plugin
+from .module import Module
+from .urls import (AmbiguousUrlException,
+ NotFoundException, UrlRule)
@@ -0,0 +1,267 @@
+# Copyright 2011 Jonathan Beluch.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from urllib import unquote_plus, quote_plus
+from urlparse import urlparse
+from cStringIO import StringIO
+import urllib2
+import xbmc
+import xbmcgui
+import xbmcplugin
+import asyncore, socket
+from urlparse import urlparse
+import pickle
+
+#def urlparse(url):
+ #protocol, remainder = url.split('://', 1)
+ #netloc, path = remainder.split('/', 1)
+ #return (protocol, netloc, '/' + path)
+
+def urlparse(url):
+ '''Takes a url and returns a 3 tuple of scheme, netloc and path'''
+ scheme, remainder = url.split('://', 1)
+ netloc, path = remainder.split('/', 1)
+ return scheme, netloc, '/' + path
+
+def clean_dict(d):
+ '''Verifies none of the values are None, otherwise XBMC wll break'''
+ if not d:
+ return None
+
+ # Filter out items whose value is None
+ ret = filter(lambda pair: pair[1] is not None, d.items())
+
+ # Make sure we have at least one item left
+ if len(ret) == 0:
+ return None
+
+ # We have at least one item, return a dict
+ return dict(ret)
+
+def pickle_dict(items):
+ ret = {}
+ for key, val in items.items():
+ if isinstance(val, basestring):
+ ret[key] = val
+ else:
+ ret['_%s' % key] = pickle.dumps(val)
+ return ret
+
+def unpickle_dict(items):
+ ret = {}
+ for key, val in items.items():
+ if key.startswith('_'):
+ ret[key[1:]] = pickle.loads(val)
+ else:
+ ret[key] = val
+ return ret
+
+def download_page(url, data=None):
+ # Must check cache using httplib2 here!
+ u = urllib2.urlopen(url, data)
+ r = u.read()
+ u.close()
+ return r
+
+def parse_url_qs(url, pickled_fragment=False):
+ '''Returns a dict of key/vals parsed from a query string. If
+ pickled_fragment=True, the method unpickles python objects stored in the
+ fragment portion of the url and adds them to the returned dictionary.
+ '''
+ parts = urlparse(url)
+ qs = parts[4]
+ fragment = parts[5]
+
+ #parse qs
+ params = parse_qs(qs)
+
+ #unpickle the fragment and update params with the pickled dict
+ if pickled_fragment and len(fragment) > 0:
+ params.update(pickle.loads(unquote_plus(fragment)))
+ return params
+
+
+def parse_qs(qs):
+ '''Takes a query string and returns a {} with key/vals. If more than
+ one instance of a key is specified, the last value will be returned.'''
+ if qs is None or len(qs) == 0:
+ return {}
+
+ pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
+ r ={}
+
+ for pair in pairs:
+ parts = pair.split('=', 1)
+ if len(parts) != 2:
+ raise ValueError, 'bad query field: %r' % (pair)
+ r[unquote_plus(parts[0])] = unquote_plus(parts[1])
+ return r
+
+class DialogProgress(xbmcgui.DialogProgress):
+ """This class is meant to extend functionality for the
+ xbmcgui.DialogProgress class. It adds an increment() method, which
+ updates the percentage of progress for one event, the step size is
+ calculated by dividing 100 by the number of events to complete. By
+ including the increment method which takes only optional parameters,
+ other classes/functions can handle updating the dialog progress bar
+ without knowing all the details such as step size, current state,
+ etc. """
+
+ def __init__(self, heading, line1='', line2='', line3='', num_steps=None):
+ #xbmcgui.DialogProgress.__init__(self)
+ super(DialogProgress, self).__init__()
+ self.lines = [line1, line2, line3]
+ self.create(heading, *self.lines)
+ self.update(0)
+ self.num_steps = num_steps
+ self.step = 0
+ self.progress = 0
+ if self.num_steps != None:
+ print 'NUMSTEPS: %d' % self.num_steps
+ self.step = 100. / self.num_steps
+
+ def set_num_items(self, num_items):
+ self.num_steps = num_items
+ self.step = 100. / self.num_steps
+
+ def increment(self, num_incr_steps=1, line1=None, line2=None, line3=None):
+ if line1 != None: self.lines[0] = line1
+ if line2 != None: self.lines[1] = line2
+ if line3 != None: self.lines[2] = line3
+ self.progress += (num_incr_steps * self.step)
+ #convert self.progress to int here,
+ self.update(int(self.progress), *self.lines)
+
+
+class HTTPClient(asyncore.dispatcher):
+ """
+ Originally based on HttpClient by Doug Hellmann from
+ http://www.doughellmann.com/PyMOTW/asyncore/
+
+ The asyncore.dispatcher is used to create lots of asynchronous
+ connections. When crawling web pages to parse video information this
+ class can significantly improve loading time.
+ """
+
+ def __init__(self, url, dp=None):
+ self.url = url
+ self.parsed_url = urlparse(url)
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ #xbmc's current version of the python interpreter doesn't include named
+ #tuple attributes, so use index instead of attribute 'netloc'
+ self.connect((self.parsed_url[1], 80))
+ self.write_buffer = 'GET %s HTTP/1.0\r\n\r\n' % self.url
+ self.read_buffer = StringIO()
+ self.dp = dp
+
+ def handle_read(self):
+ data = self.recv(8192)
+ self.read_buffer.write(data)
+
+ def handle_write(self):
+ sent = self.send(self.write_buffer)
+ self.write_buffer = self.write_buffer[sent:]
+
+ def handle_connect(self):
+ '''A warning is printing if this method isn't overridden'''
+ #check if dialog is cancelled. Since we can't set a callback method
+ #the best we can do is check the dialog status for every connect
+ #and close event, hopefully it is a fast call otherwise this will impact
+ #performance.
+ if self.dp and self.dp.iscanceled():
+ raise XBMCDialogCancelled
+
+
+ def handle_close(self):
+ if self.dp:
+ self.dp.increment()
+ if self.dp and self.dp.iscanceled():
+ raise XBMCDialogCancelled
+ self.close()
+
+ def writable(self):
+ return (len(self.write_buffer) > 0)
+
+class XBMCVideoPluginException(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class XBMCDialogCancelled(Exception):
+ pass
+
+def async_urlread(url_list, dp=None):
+ """Takes a list of urls and returns a list of responses once all of
+ the requests have finished"""
+ #create an HTTPClient for each url
+ http_clients = [HTTPClient(url, dp) for url in url_list]
+ #run the syncore loop, it will downloda all of the registered HTTPClients
+ asyncore.loop()
+
+ #if the dialog progress was cancelled we must re-raise the exception here
+ #since the asyncore.loop seems to eat the exception
+ if dp and dp.iscanceled():
+ raise XBMCDialogCancelled
+
+ #finished successfully, return a list of the responses
+ return [c.read_buffer.getvalue() for c in http_clients]
+
+def urlread(url, data=None):
+ """Helper function to reduce code needed for a simple urlopen()"""
+ f = urllib2.urlopen(url, data)
+ page_contents = f.read()
+ f.close()
+ return page_contents
+
+#Code modeled after python's urllib.unquote
+_hextochr = dict(('%02x' % i, chr(i)) for i in range(256))
+_hextochr.update(('%02X' % i, chr(i)) for i in range(256))
+
+def unhex(s):
+ '''unquote(r'abc\x20def') -> 'abc def'.'''
+ res = s.split(r'\x')
+ for i in xrange(1, len(res)):
+ item = res[i]
+ try:
+ res[i] = _hextochr[item[:2]] + item[2:]
+ except KeyError:
+ res[i] = '%' + item
+ except UnicodeDecodeError:
+ res[i] = unichr(int(item[:2], 16)) + item[2:]
+ return ''.join(res)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -0,0 +1,3 @@
+
+def main():
+ print 'Called from console.'
No changes.
@@ -0,0 +1,8 @@
+from xbmcswift import Module
+
+playlist = Module(__name__)
+
+@playlist.route('/add/')
+def add_to_playlist():
+ items = [playlist.qs_args]
+ return playlist._plugin.add_to_playlist(items)
Oops, something went wrong.

0 comments on commit bbfb990

Please sign in to comment.