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.

171 lines
3.9 KiB

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