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.

179 lines
4.0 KiB

  1. #!/usr/bin/env python
  2. import errno
  3. import itertools
  4. import os
  5. import sets
  6. import stat
  7. from DIDLLite import StorageFolder, Item, VideoItem, AudioItem, TextItem, ImageItem, Resource
  8. from twisted.web import static
  9. from twisted.python import log
  10. __all__ = [ 'registerklassfun', 'FSObject', 'FSItem', 'FSVideoItem',
  11. 'FSAudioItem', 'FSTextItem', 'FSImageItem', 'mimetoklass',
  12. 'FSDirectory',
  13. ]
  14. mimedict = static.loadMimeTypes()
  15. klassfuns = []
  16. def registerklassfun(fun):
  17. klassfuns.append(fun)
  18. def statcmp(a, b, cmpattrs = [ 'st_ino', 'st_dev', 'st_size', 'st_mtime', ]):
  19. if a is None or b is None:
  20. return False
  21. for i in cmpattrs:
  22. if getattr(a, i) != getattr(b, i):
  23. return False
  24. return True
  25. class FSObject(object):
  26. def __init__(self, path):
  27. self.FSpath = path
  28. self.pstat = None
  29. def checkUpdate(self):
  30. # need to handle no such file or directory
  31. # push it up? but still need to handle disappearing
  32. try:
  33. nstat = os.stat(self.FSpath)
  34. if statcmp(self.pstat, nstat):
  35. return self
  36. self.pstat = nstat
  37. self.doUpdate()
  38. except OSError, x:
  39. log.msg('os.stat, OSError: %s' % x)
  40. if x.errno in (errno.ENOENT, errno.ENOTDIR, errno.EPERM, ):
  41. # We can't access it anymore, delete it
  42. self.cd.delItem(self.id)
  43. return None
  44. else:
  45. raise x
  46. return self
  47. def doUpdate(self):
  48. raise NotImplementedError
  49. class FSItem(FSObject, Item):
  50. def __init__(self, *args, **kwargs):
  51. FSObject.__init__(self, kwargs['path'])
  52. del kwargs['path']
  53. mimetype = kwargs['mimetype']
  54. del kwargs['mimetype']
  55. kwargs['content'] = static.File(self.FSpath)
  56. Item.__init__(self, *args, **kwargs)
  57. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  58. self.mimetype = mimetype
  59. def doUpdate(self):
  60. self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype)
  61. self.res.size = os.path.getsize(self.FSpath)
  62. Item.doUpdate(self)
  63. class FSVideoItem(FSItem, VideoItem):
  64. pass
  65. class FSAudioItem(FSItem, AudioItem):
  66. pass
  67. class FSTextItem(FSItem, TextItem):
  68. pass
  69. class FSImageItem(FSItem, ImageItem):
  70. pass
  71. mimetoklass = {
  72. 'application/ogg': FSAudioItem,
  73. 'video': FSVideoItem,
  74. 'audio': FSAudioItem,
  75. 'text': FSTextItem,
  76. 'image': FSImageItem,
  77. }
  78. def defFS(path):
  79. fn, ext = os.path.splitext(path)
  80. ext = ext.lower()
  81. try:
  82. mt = mimedict[ext]
  83. except KeyError:
  84. log.msg('no mime-type for: %s' % path)
  85. return None, None
  86. ty = mt.split('/')[0]
  87. if mimetoklass.has_key(mt):
  88. klass = mimetoklass[mt]
  89. elif mimetoklass.has_key(ty):
  90. klass = mimetoklass[ty]
  91. else:
  92. log.msg('no item for mt: %s' % mt)
  93. return None, None
  94. return klass, { 'path': path, 'mimetype': mt }
  95. def dofileadd(cd, parent, path, name):
  96. klass = None
  97. for i in itertools.chain(klassfuns, ( defFS, )):
  98. try:
  99. klass, kwargs = i(os.path.join(path, name))
  100. if klass is not None:
  101. break
  102. except:
  103. import traceback
  104. traceback.print_exc()
  105. if klass is None:
  106. return
  107. log.msg('matched:', os.path.join(path, name), `i`)
  108. return cd.addItem(parent, klass, name, **kwargs)
  109. class FSDirectory(FSObject, StorageFolder):
  110. def __init__(self, *args, **kwargs):
  111. path = kwargs['path']
  112. del kwargs['path']
  113. StorageFolder.__init__(self, *args, **kwargs)
  114. FSObject.__init__(self, path)
  115. # mapping from path to objectID
  116. self.pathObjmap = {}
  117. def doUpdate(self):
  118. # We need to rescan this dir, and see if our children has
  119. # changed any.
  120. children = sets.Set(os.listdir(self.FSpath))
  121. for i in self.pathObjmap.keys():
  122. if i not in children:
  123. # delete
  124. self.cd.delItem(self.pathObjmap[i])
  125. del self.pathObjmap[i]
  126. for i in children:
  127. fname = os.path.join(self.FSpath, i)
  128. if i in self.pathObjmap:
  129. continue
  130. # new object
  131. if os.path.isdir(fname):
  132. # new dir
  133. nf = self.cd.addContainer(self.id, i, klass = FSDirectory, path = fname)
  134. elif os.path.isfile(fname):
  135. # new file
  136. nf = dofileadd(self.cd, self.id, self.FSpath, i)
  137. else:
  138. nf = None
  139. log.msg('skipping: %s' % fname)
  140. if nf is not None:
  141. self.pathObjmap[i] = nf
  142. # sort our children
  143. self.sort(lambda x, y: cmp(x.title, y.title))
  144. # Pass up to handle UpdateID
  145. StorageFolder.doUpdate(self)