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.

167 lines
3.8 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. #log.msg('testing:', `i`, `fsname`, `fobj`)
  94. klass, kwargs = i(fsname, fobj)
  95. if klass is not None:
  96. break
  97. except:
  98. #import traceback
  99. #traceback.print_exc(file=log.logfile)
  100. pass
  101. if klass is None:
  102. return
  103. #log.msg('matched:', os.path.join(path, name), `i`, `klass`)
  104. return cd.addItem(parent, klass, name, **kwargs)
  105. class FSDirectory(FSObject, StorageFolder):
  106. def __init__(self, *args, **kwargs):
  107. path = kwargs['path']
  108. del kwargs['path']
  109. StorageFolder.__init__(self, *args, **kwargs)
  110. FSObject.__init__(self, path)
  111. # mapping from path to objectID
  112. self.pathObjmap = {}
  113. def doUpdate(self):
  114. # We need to rescan this dir, and see if our children has
  115. # changed any.
  116. doupdate = False
  117. children = sets.Set(os.listdir(self.FSpath))
  118. for i in self.pathObjmap.keys():
  119. if i not in children:
  120. doupdate = True
  121. # delete
  122. self.cd.delItem(self.pathObjmap[i])
  123. del self.pathObjmap[i]
  124. for i in children:
  125. if i in self.pathObjmap:
  126. continue
  127. # new object
  128. nf = dofileadd(self.cd, self.id, self.FSpath, i)
  129. if nf is not None:
  130. doupdate = True
  131. self.pathObjmap[i] = nf
  132. # sort our children
  133. self.sort(lambda x, y: cmp(x.title, y.title))
  134. # Pass up to handle UpdateID
  135. if doupdate:
  136. StorageFolder.doUpdate(self)