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.

301 lines
7.1 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2006 John-Mark Gurney <gurney_j@resnet.uroegon.edu>
  3. #
  4. # $Id$
  5. #
  6. import itertools
  7. import os.path
  8. import sets
  9. import time
  10. import iterzipfile
  11. zipfile = iterzipfile
  12. import itertarfile
  13. tarfile = itertarfile
  14. import FileDIDL
  15. from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource
  16. from FSStorage import FSObject, registerklassfun
  17. from twisted.python import log, threadable
  18. from twisted.spread import pb
  19. from twisted.web import http
  20. from twisted.web import server
  21. from twisted.web import resource
  22. def inserthierdict(d, name, obj):
  23. if not name:
  24. return
  25. i = name.find('/')
  26. if i == -1:
  27. d[name] = obj
  28. return
  29. dname = name[:i]
  30. rname = name[i + 1:]
  31. # remaining path components
  32. try:
  33. inserthierdict(d[dname], rname, obj)
  34. except KeyError:
  35. d[dname] = {}
  36. inserthierdict(d[dname], rname, obj)
  37. def buildNameHier(names, objs):
  38. ret = {}
  39. for n, o in itertools.izip(names, objs):
  40. inserthierdict(ret, n, o)
  41. return ret
  42. class ZipFileTransfer(pb.Viewable):
  43. def __init__(self, zf, name, request):
  44. self.zf = zf
  45. self.size = zf.getinfo(name).file_size
  46. self.iter = zf.readiter(name)
  47. self.request = request
  48. self.written = 0
  49. request.registerProducer(self, 0)
  50. def resumeProducing(self):
  51. if not self.request:
  52. return
  53. # get data and write to request.
  54. try:
  55. data = self.iter.next()
  56. if data:
  57. self.written += len(data)
  58. # this .write will spin the reactor, calling
  59. # .doWrite and then .resumeProducing again, so
  60. # be prepared for a re-entrant call
  61. self.request.write(data)
  62. except StopIteration:
  63. if self.request:
  64. self.request.unregisterProducer()
  65. self.request.finish()
  66. self.request = None
  67. def pauseProducing(self):
  68. pass
  69. def stopProducing(self):
  70. # close zipfile
  71. self.request = None
  72. # Remotely relay producer interface.
  73. def view_resumeProducing(self, issuer):
  74. self.resumeProducing()
  75. def view_pauseProducing(self, issuer):
  76. self.pauseProducing()
  77. def view_stopProducing(self, issuer):
  78. self.stopProducing()
  79. synchronized = ['resumeProducing', 'stopProducing']
  80. threadable.synchronize(ZipFileTransfer)
  81. class ZipResource(resource.Resource):
  82. # processors = {}
  83. isLeaf = True
  84. def __init__(self, zf, name, mt):
  85. resource.Resource.__init__(self)
  86. self.zf = zf
  87. self.zi = zf.getinfo(name)
  88. self.name = name
  89. self.mt = mt
  90. def getFileSize(self):
  91. return self.zi.file_size
  92. def render(self, request):
  93. request.setHeader('content-type', self.mt)
  94. # We could possibly send the deflate data directly!
  95. if None and self.encoding:
  96. request.setHeader('content-encoding', self.encoding)
  97. if request.setLastModified(time.mktime(list(self.zi.date_time) +
  98. [ 0, 0, -1])) is http.CACHED:
  99. return ''
  100. request.setHeader('content-length', str(self.getFileSize()))
  101. if request.method == 'HEAD':
  102. return ''
  103. # return data
  104. ZipFileTransfer(self.zf, self.name, request)
  105. # and make sure the connection doesn't get closed
  106. return server.NOT_DONE_YET
  107. class ZipItem:
  108. '''Basic zip stuff initalization'''
  109. def __init__(self, *args, **kwargs):
  110. self.zo = kwargs['zo']
  111. del kwargs['zo']
  112. self.zf = kwargs['zf']
  113. del kwargs['zf']
  114. self.name = kwargs['name']
  115. del kwargs['name']
  116. def checkUpdate(self):
  117. self.doUpdate()
  118. return self.zo.checkUpdate()
  119. class ZipFile(ZipItem, Item):
  120. def __init__(self, *args, **kwargs):
  121. self.mimetype = kwargs['mimetype']
  122. del kwargs['mimetype']
  123. ZipItem.__init__(self, *args, **kwargs)
  124. self.zi = self.zf.getinfo(self.name)
  125. kwargs['content'] = ZipResource(self.zf, self.name,
  126. self.mimetype)
  127. Item.__init__(self, *args, **kwargs)
  128. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  129. self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype)
  130. self.res.size = self.zi.file_size
  131. def doUpdate(self):
  132. pass
  133. class ZipChildDir(ZipItem, StorageFolder):
  134. '''This is to represent a child dir of the zip file.'''
  135. def __init__(self, *args, **kwargs):
  136. self.hier = kwargs['hier']
  137. del kwargs['hier']
  138. ZipItem.__init__(self, *args, **kwargs)
  139. del kwargs['zf'], kwargs['zo'], kwargs['name']
  140. StorageFolder.__init__(self, *args, **kwargs)
  141. # mapping from path to objectID
  142. self.pathObjmap = {}
  143. def doUpdate(self):
  144. doupdate = False
  145. children = sets.Set(self.hier.keys())
  146. for i in self.pathObjmap.keys():
  147. if i not in children:
  148. # delete
  149. doupdate = True
  150. self.cd.delItem(self.pathObjmap[i])
  151. del self.pathObjmap[i]
  152. for i in children:
  153. if i in self.pathObjmap:
  154. continue
  155. # new object
  156. pathname = os.path.join(self.name, i)
  157. if isinstance(self.hier[i], dict):
  158. # must be a dir
  159. self.pathObjmap[i] = self.cd.addItem(self.id,
  160. ZipChildDir, i, zf = self.zf, zo = self,
  161. name = pathname, hier = self.hier[i])
  162. else:
  163. klass, mt = FileDIDL.buildClassMT(ZipFile, i)
  164. if klass is None:
  165. continue
  166. self.pathObjmap[i] = self.cd.addItem(self.id,
  167. klass, i, zf = self.zf, zo = self,
  168. name = pathname, mimetype = mt)
  169. doupdate = True
  170. # sort our children
  171. self.sort(lambda x, y: cmp(x.title, y.title))
  172. if doupdate:
  173. StorageFolder.doUpdate(self)
  174. def __repr__(self):
  175. return '<ZipChildDir: len: %d>' % len(self.pathObjmap)
  176. def genZipFile(path):
  177. try:
  178. return zipfile.ZipFile(path)
  179. except:
  180. #import traceback
  181. #traceback.print_exc(file=log.logfile)
  182. pass
  183. try:
  184. # Try to see if it's a tar file
  185. if path[-2:] == 'gz':
  186. comp = tarfile.TAR_GZIPPED
  187. elif path[-3:] == 'bz2':
  188. comp = tarfile.TAR_BZ2
  189. else:
  190. comp = tarfile.TAR_PLAIN
  191. return tarfile.TarFileCompat(path, compression=comp)
  192. except:
  193. #import traceback
  194. #traceback.print_exc(file=log.logfile)
  195. raise
  196. class ZipObject(FSObject, StorageFolder):
  197. def __init__(self, *args, **kwargs):
  198. '''If a zip argument is passed it, use that as the zip archive.'''
  199. path = kwargs['path']
  200. del kwargs['path']
  201. StorageFolder.__init__(self, *args, **kwargs)
  202. FSObject.__init__(self, path)
  203. # mapping from path to objectID
  204. self.pathObjmap = {}
  205. def doUpdate(self):
  206. # open the zipfile as necessary.
  207. self.zip = genZipFile(self.FSpath)
  208. hier = buildNameHier(self.zip.namelist(), self.zip.infolist())
  209. doupdate = False
  210. children = sets.Set(hier.keys())
  211. for i in self.pathObjmap.keys():
  212. if i not in children:
  213. doupdate = True
  214. # delete
  215. self.cd.delItem(self.pathObjmap[i])
  216. del self.pathObjmap[i]
  217. for i in children:
  218. if i in self.pathObjmap:
  219. continue
  220. # new object
  221. if isinstance(hier[i], dict):
  222. # must be a dir
  223. self.pathObjmap[i] = self.cd.addItem(self.id,
  224. ZipChildDir, i, zf = self.zip, zo = self,
  225. name = i, hier = hier[i])
  226. else:
  227. klass, mt = FileDIDL.buildClassMT(ZipFile, i)
  228. if klass is None:
  229. continue
  230. self.pathObjmap[i] = self.cd.addItem(self.id,
  231. klass, i, zf = self.zip, zo = self,
  232. name = i, mimetype = mt)
  233. doupdate = True
  234. # sort our children
  235. self.sort(lambda x, y: cmp(x.title, y.title))
  236. if doupdate:
  237. StorageFolder.doUpdate(self)
  238. def __repr__(self):
  239. return '<ZipObject: path: %s>' % self.FSpath
  240. def detectzipfile(path, fobj):
  241. try:
  242. genZipFile(path)
  243. except:
  244. return None, None
  245. return ZipObject, { 'path': path }
  246. registerklassfun(detectzipfile)