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.

162 lines
3.7 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. class FSItem(FSObject, Item):
  55. def __init__(self, *args, **kwargs):
  56. FSObject.__init__(self, kwargs['path'])
  57. del kwargs['path']
  58. mimetype = kwargs['mimetype']
  59. del kwargs['mimetype']
  60. kwargs['content'] = static.File(self.FSpath)
  61. Item.__init__(self, *args, **kwargs)
  62. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  63. self.mimetype = mimetype
  64. def doUpdate(self):
  65. self.res = Resource(self.url, 'http-get:*:%s:*' % self.mimetype)
  66. self.res.size = os.path.getsize(self.FSpath)
  67. Item.doUpdate(self)
  68. def defFS(path, fobj):
  69. if os.path.isdir(path):
  70. # new dir
  71. return FSDirectory, { 'path': path }
  72. elif os.path.isfile(path):
  73. # new file - fall through to below
  74. pass
  75. else:
  76. log.msg('skipping (not dir or reg): %s' % path)
  77. return None, None
  78. klass, mt = FileDIDL.buildClassMT(FSItem, path)
  79. return klass, { 'path': path, 'mimetype': mt }
  80. def dofileadd(cd, parent, path, name):
  81. klass = None
  82. fsname = os.path.join(path, name)
  83. try:
  84. fobj = open(fsname)
  85. except:
  86. fobj = None
  87. for i in itertools.chain(klassfuns, ( defFS, )):
  88. try:
  89. try:
  90. fobj.seek(0) # incase the call expects a clean file
  91. except:
  92. pass
  93. klass, kwargs = i(fsname, fobj)
  94. if klass is not None:
  95. break
  96. except:
  97. #import traceback
  98. #traceback.print_exc(file=log.logfile)
  99. pass
  100. if klass is None:
  101. return
  102. log.msg('matched:', os.path.join(path, name), `i`, `klass`)
  103. return cd.addItem(parent, klass, name, **kwargs)
  104. class FSDirectory(FSObject, StorageFolder):
  105. def __init__(self, *args, **kwargs):
  106. path = kwargs['path']
  107. del kwargs['path']
  108. StorageFolder.__init__(self, *args, **kwargs)
  109. FSObject.__init__(self, path)
  110. # mapping from path to objectID
  111. self.pathObjmap = {}
  112. def doUpdate(self):
  113. # We need to rescan this dir, and see if our children has
  114. # changed any.
  115. children = sets.Set(os.listdir(self.FSpath))
  116. for i in self.pathObjmap.keys():
  117. if i not in children:
  118. # delete
  119. self.cd.delItem(self.pathObjmap[i])
  120. del self.pathObjmap[i]
  121. for i in children:
  122. if i in self.pathObjmap:
  123. continue
  124. # new object
  125. nf = dofileadd(self.cd, self.id, self.FSpath, i)
  126. if nf is not None:
  127. self.pathObjmap[i] = nf
  128. # sort our children
  129. self.sort(lambda x, y: cmp(x.title, y.title))
  130. # Pass up to handle UpdateID
  131. StorageFolder.doUpdate(self)