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.

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