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.

324 lines
7.3 KiB

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