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.

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