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.

184 lines
4.1 KiB

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