Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
jbeluch committed Jun 29, 2011
0 parents commit bbfb990
Show file tree
Hide file tree
Showing 19 changed files with 1,150 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
notes
tmux.sh
*.pyo
*.pyc
*.swp
*.swo
Empty file added README.md
Empty file.
34 changes: 34 additions & 0 deletions setup.py
@@ -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',
]
},
)
6 changes: 6 additions & 0 deletions xbmcswift/__init__.py
@@ -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)
267 changes: 267 additions & 0 deletions xbmcswift/common.py
@@ -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)























3 changes: 3 additions & 0 deletions xbmcswift/console.py
@@ -0,0 +1,3 @@

def main():
print 'Called from console.'
Empty file added xbmcswift/ext/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions xbmcswift/ext/playlist.py
@@ -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)

0 comments on commit bbfb990

Please sign in to comment.