|
|
@@ -0,0 +1,268 @@ |
|
|
|
#!/usr/bin/env python |
|
|
|
# Copyright 2008 John-Mark Gurney <jmg@funktaht.com> |
|
|
|
'''PVR Interface''' |
|
|
|
|
|
|
|
__version__ = '$Change: 1109 $' |
|
|
|
# $Id: //depot/python/pymeds/main/shoutcast.py#13 $ |
|
|
|
|
|
|
|
from DIDLLite import Container, Item, VideoItem, Resource |
|
|
|
from FSStorage import registerklassfun |
|
|
|
|
|
|
|
import os.path |
|
|
|
import time |
|
|
|
import twisted.web |
|
|
|
from twisted.internet import reactor |
|
|
|
from twisted.python import log |
|
|
|
|
|
|
|
def getPage(url, contextFactory=None, *args, **kwargs): |
|
|
|
"""Download a web page as a string. |
|
|
|
|
|
|
|
Download a page. Return the HTTPClientFactory, which will |
|
|
|
callback with a page (as a string) or errback with a |
|
|
|
description of the error. |
|
|
|
|
|
|
|
See HTTPClientFactory to see what extra args can be passed. |
|
|
|
""" |
|
|
|
from twisted.web.client import _parse, HTTPClientFactory |
|
|
|
|
|
|
|
scheme, host, port, path = _parse(url) |
|
|
|
factory = HTTPClientFactory(url, *args, **kwargs) |
|
|
|
if scheme == 'https': |
|
|
|
from twisted.internet import ssl |
|
|
|
if contextFactory is None: |
|
|
|
contextFactory = ssl.ClientContextFactory() |
|
|
|
reactor.connectSSL(host, port, factory, contextFactory) |
|
|
|
else: |
|
|
|
reactor.connectTCP(host, port, factory) |
|
|
|
return factory |
|
|
|
|
|
|
|
class PYVRShow(VideoItem): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
baseurl = kwargs['url'] |
|
|
|
self.info = kwargs['info'] |
|
|
|
del kwargs['info'], kwargs['url'] |
|
|
|
|
|
|
|
VideoItem.__init__(self, *args, **kwargs) |
|
|
|
|
|
|
|
url = self.info['link'] |
|
|
|
sc = urlparse.urlparse(url) |
|
|
|
if not sc: |
|
|
|
# need to combine w/ base url |
|
|
|
url = urlparse.urljoin(baseurl, url) |
|
|
|
self.res = Resource(url, |
|
|
|
'http-get:*:%s:*' % self.info['mimetype']) |
|
|
|
#self.res.size = self.chapter.size |
|
|
|
|
|
|
|
def doUpdate(self): |
|
|
|
pass |
|
|
|
|
|
|
|
import xml.sax |
|
|
|
import xml.sax.handler |
|
|
|
from xml.sax.saxutils import unescape |
|
|
|
|
|
|
|
class RecordingXML(xml.sax.handler.ContentHandler): |
|
|
|
def __init__(self): |
|
|
|
self.shows = {} |
|
|
|
self.data = None |
|
|
|
|
|
|
|
def characters(self, chars): |
|
|
|
if self.data is not None: |
|
|
|
self.data.append(chars) |
|
|
|
|
|
|
|
def startElement(self, name, attrs): |
|
|
|
if name in ('title', 'subtitle', 'mimetype', 'link', 'delete'): |
|
|
|
self.data = [] |
|
|
|
self.curel = name |
|
|
|
elif name == 'record': |
|
|
|
self.currec = {} |
|
|
|
|
|
|
|
def endElement(self, name): |
|
|
|
if name in ('title', 'subtitle', 'mimetype', 'link', 'delete'): |
|
|
|
data = unescape(''.join(self.data)) |
|
|
|
self.currec[self.curel] = data |
|
|
|
elif name == 'record': |
|
|
|
rec = self.currec |
|
|
|
try: |
|
|
|
self.shows[rec['title']].append(rec) |
|
|
|
except KeyError: |
|
|
|
self.shows[rec['title']] = [ rec ] |
|
|
|
|
|
|
|
self.data = None |
|
|
|
|
|
|
|
def recxmltoobj(page): |
|
|
|
obj = RecordingXML() |
|
|
|
xml.sax.parseString(page, obj) |
|
|
|
|
|
|
|
return obj.shows |
|
|
|
|
|
|
|
class PYVRShows(Container): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
self.pyvr = kwargs['pyvr'] |
|
|
|
del kwargs['pyvr'] |
|
|
|
self.show = kwargs['show'] |
|
|
|
del kwargs['show'] |
|
|
|
|
|
|
|
Container.__init__(self, *args, **kwargs) |
|
|
|
|
|
|
|
self.pathObjmap = {} |
|
|
|
self.shows = {} |
|
|
|
self.lastmodified = None |
|
|
|
|
|
|
|
def checkUpdate(self): |
|
|
|
self.pyvr.checkUpdate() |
|
|
|
if self.pyvr.lastmodified != self.lastmodified: |
|
|
|
self.doUpdate(self) |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def getunique(eps, ep): |
|
|
|
i = 1 |
|
|
|
while True: |
|
|
|
title = '%s Copy %d' % (ep['subtitle'], i) |
|
|
|
if not ret.has_key(title): |
|
|
|
return title |
|
|
|
i += 1 |
|
|
|
|
|
|
|
@staticmethod |
|
|
|
def eplisttodict(eps): |
|
|
|
ret = {} |
|
|
|
for i in eps: |
|
|
|
title = i['subtitle'] |
|
|
|
if ret.has_key(title): |
|
|
|
print 'WARNING: dup:', `i`, `ret[title]` |
|
|
|
title = self.getunique(ret, i) |
|
|
|
ret[title] = i |
|
|
|
|
|
|
|
return ret |
|
|
|
|
|
|
|
def doUpdate(self): |
|
|
|
nl = self.eplisttodict(self.pyvr.shows[self.show]) |
|
|
|
|
|
|
|
doupdate = False |
|
|
|
for i in self.pathObjmap.keys(): |
|
|
|
if i not in nl: |
|
|
|
# delete |
|
|
|
doupdate = True |
|
|
|
self.cd.delItem(self.pathObjmap[i]) |
|
|
|
del self.pathObjmap[i] |
|
|
|
|
|
|
|
for i in nl: |
|
|
|
if i in self.pathObjmap and self.shows[i] == nl[i]: |
|
|
|
continue |
|
|
|
doupdate = True |
|
|
|
if i in self.pathObjmap: |
|
|
|
# changed |
|
|
|
self.cd.delItem(self.pathObjmap[i]) |
|
|
|
self.pathObjmap[i] = self.cd.addItem(self.id, |
|
|
|
PYVRShow, i, url=self.pyvr.url, info=nl[i]) |
|
|
|
|
|
|
|
self.shows = nl |
|
|
|
|
|
|
|
# sort our children |
|
|
|
self.sort(lambda x, y: cmp(x.title, y.title)) |
|
|
|
if doupdate: |
|
|
|
Container.doUpdate(self) |
|
|
|
|
|
|
|
class PYVR(Container): |
|
|
|
def __init__(self, *args, **kwargs): |
|
|
|
self.url = kwargs['url'] |
|
|
|
del kwargs['url'] |
|
|
|
|
|
|
|
Container.__init__(self, *args, **kwargs) |
|
|
|
|
|
|
|
self.pathObjmap = {} |
|
|
|
self.isPend = False |
|
|
|
self.lastmodified = None |
|
|
|
self.newobjs = None |
|
|
|
self.objs = {} |
|
|
|
self.lastcheck = 0 |
|
|
|
|
|
|
|
def checkUpdate(self): |
|
|
|
if self.isPend: |
|
|
|
print 'already' |
|
|
|
raise self.pend |
|
|
|
|
|
|
|
if time.time() - self.lastcheck < 5: |
|
|
|
print 'escape' |
|
|
|
return |
|
|
|
|
|
|
|
# Check to see if any changes have been made |
|
|
|
self.isPend = True |
|
|
|
self.lastcheck = time.time() |
|
|
|
self.page = getPage(self.url, method='HEAD') |
|
|
|
self.page.deferred.addCallback(self.doCheck) |
|
|
|
self.pend = self.page.deferred |
|
|
|
|
|
|
|
print 'doing raise' |
|
|
|
raise self.pend |
|
|
|
|
|
|
|
def doCheck(self, x): |
|
|
|
print 'got check' |
|
|
|
print `self.page.response_headers` |
|
|
|
slm = self.page.response_headers['last-modified'] |
|
|
|
if slm == self.lastmodified: |
|
|
|
# Page the same, don't do anything |
|
|
|
self.isPend = False |
|
|
|
return |
|
|
|
|
|
|
|
self.page = getPage(self.url) |
|
|
|
self.page.deferred.addCallback(self.parsePage) |
|
|
|
self.pend = self.page.deferred |
|
|
|
|
|
|
|
return self.pend |
|
|
|
|
|
|
|
def parsePage(self, page): |
|
|
|
print 'parsing' |
|
|
|
slm = self.page.response_headers['last-modified'] |
|
|
|
self.lastmodified = slm |
|
|
|
self.isPend = False |
|
|
|
del self.page |
|
|
|
del self.pend |
|
|
|
|
|
|
|
self.newobjs = recxmltoobj(page) |
|
|
|
self.doUpdate() |
|
|
|
|
|
|
|
def doUpdate(self): |
|
|
|
if self.newobjs is None: |
|
|
|
import traceback |
|
|
|
traceback.print_stack(file=log.logfile) |
|
|
|
return |
|
|
|
|
|
|
|
nl = self.newobjs |
|
|
|
|
|
|
|
doupdate = False |
|
|
|
for i in self.pathObjmap.keys(): |
|
|
|
if i not in nl: |
|
|
|
# delete |
|
|
|
doupdate = True |
|
|
|
self.cd.delItem(self.pathObjmap[i]) |
|
|
|
del self.pathObjmap[i] |
|
|
|
|
|
|
|
# This data is referenced when adding new shows |
|
|
|
self.shows = nl |
|
|
|
for i in nl: |
|
|
|
if i in self.pathObjmap: |
|
|
|
continue |
|
|
|
doupdate = True |
|
|
|
try: |
|
|
|
self.pathObjmap[i] = self.cd.addItem(self.id, |
|
|
|
PYVRShows, i, show=i, pyvr=self) |
|
|
|
except: |
|
|
|
import traceback |
|
|
|
traceback.print_exc(file=log.logfile) |
|
|
|
raise |
|
|
|
|
|
|
|
self.newobjs = None |
|
|
|
|
|
|
|
# sort our children |
|
|
|
self.sort(lambda x, y: cmp(x.title, y.title)) |
|
|
|
if doupdate: |
|
|
|
Container.doUpdate(self) |
|
|
|
|
|
|
|
def detectpyvrfile(path, fobj): |
|
|
|
bn = os.path.basename(path) |
|
|
|
if bn == 'PYVR': |
|
|
|
print 'detected' |
|
|
|
return PYVR, { 'url': fobj.readline().strip() } |
|
|
|
return None, None |
|
|
|
|
|
|
|
registerklassfun(detectpyvrfile) |