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.

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