A Python UPnP Media Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
6.1 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2008 John-Mark Gurney <jmg@funktaht.com>
  3. '''PVR Interface'''
  4. __version__ = '$Change: 1109 $'
  5. # $Id: //depot/python/pymeds/main/shoutcast.py#13 $
  6. from DIDLLite import Container, Item, VideoItem, Resource
  7. from FSStorage import registerklassfun
  8. import os.path
  9. import time
  10. import twisted.web
  11. from twisted.internet import reactor
  12. from twisted.python import log
  13. def getPage(url, contextFactory=None, *args, **kwargs):
  14. """Download a web page as a string.
  15. Download a page. Return the HTTPClientFactory, which will
  16. callback with a page (as a string) or errback with a
  17. description of the error.
  18. See HTTPClientFactory to see what extra args can be passed.
  19. """
  20. from twisted.web.client import _parse, HTTPClientFactory
  21. scheme, host, port, path = _parse(url)
  22. factory = HTTPClientFactory(url, *args, **kwargs)
  23. if scheme == 'https':
  24. from twisted.internet import ssl
  25. if contextFactory is None:
  26. contextFactory = ssl.ClientContextFactory()
  27. reactor.connectSSL(host, port, factory, contextFactory)
  28. else:
  29. reactor.connectTCP(host, port, factory)
  30. return factory
  31. class PYVRShow(VideoItem):
  32. def __init__(self, *args, **kwargs):
  33. baseurl = kwargs['url']
  34. self.info = kwargs['info']
  35. del kwargs['info'], kwargs['url']
  36. VideoItem.__init__(self, *args, **kwargs)
  37. url = self.info['link']
  38. sc = urlparse.urlparse(url)
  39. if not sc:
  40. # need to combine w/ base url
  41. url = urlparse.urljoin(baseurl, url)
  42. self.res = Resource(url,
  43. 'http-get:*:%s:*' % self.info['mimetype'])
  44. #self.res.size = self.chapter.size
  45. def doUpdate(self):
  46. pass
  47. import xml.sax
  48. import xml.sax.handler
  49. from xml.sax.saxutils import unescape
  50. class RecordingXML(xml.sax.handler.ContentHandler):
  51. def __init__(self):
  52. self.shows = {}
  53. self.data = None
  54. def characters(self, chars):
  55. if self.data is not None:
  56. self.data.append(chars)
  57. def startElement(self, name, attrs):
  58. if name in ('title', 'subtitle', 'mimetype', 'link', 'delete'):
  59. self.data = []
  60. self.curel = name
  61. elif name == 'record':
  62. self.currec = {}
  63. def endElement(self, name):
  64. if name in ('title', 'subtitle', 'mimetype', 'link', 'delete'):
  65. data = unescape(''.join(self.data))
  66. self.currec[self.curel] = data
  67. elif name == 'record':
  68. rec = self.currec
  69. try:
  70. self.shows[rec['title']].append(rec)
  71. except KeyError:
  72. self.shows[rec['title']] = [ rec ]
  73. self.data = None
  74. def recxmltoobj(page):
  75. obj = RecordingXML()
  76. xml.sax.parseString(page, obj)
  77. return obj.shows
  78. class PYVRShows(Container):
  79. def __init__(self, *args, **kwargs):
  80. self.pyvr = kwargs['pyvr']
  81. del kwargs['pyvr']
  82. self.show = kwargs['show']
  83. del kwargs['show']
  84. Container.__init__(self, *args, **kwargs)
  85. self.pathObjmap = {}
  86. self.shows = {}
  87. self.lastmodified = None
  88. def checkUpdate(self):
  89. self.pyvr.checkUpdate()
  90. if self.pyvr.lastmodified != self.lastmodified:
  91. self.doUpdate(self)
  92. @staticmethod
  93. def getunique(eps, ep):
  94. i = 1
  95. while True:
  96. title = '%s Copy %d' % (ep['subtitle'], i)
  97. if not ret.has_key(title):
  98. return title
  99. i += 1
  100. @staticmethod
  101. def eplisttodict(eps):
  102. ret = {}
  103. for i in eps:
  104. title = i['subtitle']
  105. if ret.has_key(title):
  106. print 'WARNING: dup:', `i`, `ret[title]`
  107. title = self.getunique(ret, i)
  108. ret[title] = i
  109. return ret
  110. def doUpdate(self):
  111. nl = self.eplisttodict(self.pyvr.shows[self.show])
  112. doupdate = False
  113. for i in self.pathObjmap.keys():
  114. if i not in nl:
  115. # delete
  116. doupdate = True
  117. self.cd.delItem(self.pathObjmap[i])
  118. del self.pathObjmap[i]
  119. for i in nl:
  120. if i in self.pathObjmap and self.shows[i] == nl[i]:
  121. continue
  122. doupdate = True
  123. if i in self.pathObjmap:
  124. # changed
  125. self.cd.delItem(self.pathObjmap[i])
  126. self.pathObjmap[i] = self.cd.addItem(self.id,
  127. PYVRShow, i, url=self.pyvr.url, info=nl[i])
  128. self.shows = nl
  129. # sort our children
  130. self.sort(lambda x, y: cmp(x.title, y.title))
  131. if doupdate:
  132. Container.doUpdate(self)
  133. class PYVR(Container):
  134. def __init__(self, *args, **kwargs):
  135. self.url = kwargs['url']
  136. del kwargs['url']
  137. Container.__init__(self, *args, **kwargs)
  138. self.pathObjmap = {}
  139. self.isPend = False
  140. self.lastmodified = None
  141. self.newobjs = None
  142. self.objs = {}
  143. self.lastcheck = 0
  144. def checkUpdate(self):
  145. if self.isPend:
  146. print 'already'
  147. raise self.pend
  148. if time.time() - self.lastcheck < 5:
  149. print 'escape'
  150. return
  151. # Check to see if any changes have been made
  152. self.isPend = True
  153. self.lastcheck = time.time()
  154. self.page = getPage(self.url, method='HEAD')
  155. self.page.deferred.addCallback(self.doCheck)
  156. self.pend = self.page.deferred
  157. print 'doing raise'
  158. raise self.pend
  159. def doCheck(self, x):
  160. print 'got check'
  161. print `self.page.response_headers`
  162. slm = self.page.response_headers['last-modified']
  163. if slm == self.lastmodified:
  164. # Page the same, don't do anything
  165. self.isPend = False
  166. return
  167. self.page = getPage(self.url)
  168. self.page.deferred.addCallback(self.parsePage)
  169. self.pend = self.page.deferred
  170. return self.pend
  171. def parsePage(self, page):
  172. print 'parsing'
  173. slm = self.page.response_headers['last-modified']
  174. self.lastmodified = slm
  175. self.isPend = False
  176. del self.page
  177. del self.pend
  178. self.newobjs = recxmltoobj(page)
  179. self.doUpdate()
  180. def doUpdate(self):
  181. if self.newobjs is None:
  182. import traceback
  183. traceback.print_stack(file=log.logfile)
  184. return
  185. nl = self.newobjs
  186. doupdate = False
  187. for i in self.pathObjmap.keys():
  188. if i not in nl:
  189. # delete
  190. doupdate = True
  191. self.cd.delItem(self.pathObjmap[i])
  192. del self.pathObjmap[i]
  193. # This data is referenced when adding new shows
  194. self.shows = nl
  195. for i in nl:
  196. if i in self.pathObjmap:
  197. continue
  198. doupdate = True
  199. try:
  200. self.pathObjmap[i] = self.cd.addItem(self.id,
  201. PYVRShows, i, show=i, pyvr=self)
  202. except:
  203. import traceback
  204. traceback.print_exc(file=log.logfile)
  205. raise
  206. self.newobjs = None
  207. # sort our children
  208. self.sort(lambda x, y: cmp(x.title, y.title))
  209. if doupdate:
  210. Container.doUpdate(self)
  211. def detectpyvrfile(path, fobj):
  212. bn = os.path.basename(path)
  213. if bn == 'PYVR':
  214. print 'detected'
  215. return PYVR, { 'url': fobj.readline().strip() }
  216. return None, None
  217. registerklassfun(detectpyvrfile)