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.

309 lines
7.2 KiB

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