Browse Source

switch to using tabs instead of spaces...

[git-p4: depot-paths = "//depot/": change = 763]
replace/4e84fdb41ea781c7a8f872baa423e8b3be4045a7
John-Mark Gurney 19 years ago
parent
commit
312d124260
8 changed files with 561 additions and 575 deletions
  1. +5
    -5
      ConnectionManager.py
  2. +152
    -152
      ContentDirectory.py
  3. +213
    -213
      DIDLLite.py
  4. +148
    -160
      SSDP.py
  5. +9
    -9
      browse-zms.py
  6. +28
    -28
      cdsclient
  7. +5
    -7
      upnp.py
  8. +1
    -1
      xmlpretty

+ 5
- 5
ConnectionManager.py View File

@@ -10,10 +10,10 @@ from twisted.web import resource, static, soap
from upnp import UPnPPublisher from upnp import UPnPPublisher


class ConnectionManagerControl(UPnPPublisher): class ConnectionManagerControl(UPnPPublisher):
pass pass


class ConnectionManagerServer(resource.Resource): class ConnectionManagerServer(resource.Resource):
def __init__(self): def __init__(self):
resource.Resource.__init__(self) resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('connection-manager-scpd.xml')) self.putChild('scpd.xml', static.File('connection-manager-scpd.xml'))
self.putChild('control', ConnectionManagerControl()) self.putChild('control', ConnectionManagerControl())

+ 152
- 152
ContentDirectory.py View File

@@ -14,9 +14,9 @@
# twisted swallows the tracebacks. At the moment I'm going: # twisted swallows the tracebacks. At the moment I'm going:
# #
# try: # try:
# .... # ....
# except: # except:
# traceback.print_exc(file = log.logfile) # traceback.print_exc(file = log.logfile)
# #


from twisted.python import log from twisted.python import log
@@ -30,208 +30,208 @@ import traceback
from urllib import quote from urllib import quote


class ContentDirectoryControl(UPnPPublisher, dict): class ContentDirectoryControl(UPnPPublisher, dict):
"""This class implements the CDS actions over SOAP.""" """This class implements the CDS actions over SOAP."""
def getnextID(self):
def getnextID(self): ret = str(self.nextID)
ret = str(self.nextID) self.nextID += 1
self.nextID += 1 return ret
return ret def addContainer(self, parent, title, klass = Container, **kwargs):
ret = self.addItem(parent, klass, title, **kwargs)
def addContainer(self, parent, title, klass = Container, **kwargs): self.children[ret] = self[ret]
ret = self.addItem(parent, klass, title, **kwargs) return ret
self.children[ret] = self[ret] def addItem(self, parent, klass, *args, **kwargs):
return ret assert isinstance(self[parent], Container)
nid = self.getnextID()
def addItem(self, parent, klass, *args, **kwargs): i = klass(self, nid, parent, *args, **kwargs)
assert isinstance(self[parent], Container) self.children[parent].append(i)
nid = self.getnextID() self[i.id] = i
i = klass(self, nid, parent, *args, **kwargs) return i.id
self.children[parent].append(i) def delItem(self, id):
self[i.id] = i
return i.id
def delItem(self, id):
if isinstance(self[id], Container): if isinstance(self[id], Container):
for i in self.children[id]: for i in self.children[id]:
self.delItem(i) self.delItem(i)
assert len(self.children[id]) == 0 assert len(self.children[id]) == 0
del self.children[id] del self.children[id]
del self[id] del self[id]


def getchildren(self, item): def getchildren(self, item):
assert isinstance(self[item], Container) assert isinstance(self[item], Container)
return self.children[item][:] return self.children[item][:]


def __init__(self, title, *args): def __init__(self, title, *args):
super(ContentDirectoryControl, self).__init__(*args) super(ContentDirectoryControl, self).__init__(*args)
fakeparent = '-1' fakeparent = '-1'
self.nextID = 0 self.nextID = 0
self.children = { fakeparent: []} self.children = { fakeparent: []}
self.needupdate = False self.needupdate = False
self.updateId = 0 self.updateId = 0
self[fakeparent] = Container(None, '-1', 'fake') self[fakeparent] = Container(None, '-1', 'fake')
root = self.addContainer(fakeparent, title) root = self.addContainer(fakeparent, title)
assert root == '0' assert root == '0'
del self[fakeparent] del self[fakeparent]
del self.children[fakeparent] del self.children[fakeparent]


def doupdate(self): def doupdate(self):
if self.needupdate: if self.needupdate:
self.needupdate += 1 self.needupdate += 1
self.needupdate = False self.needupdate = False


# Required actions # Required actions


def soap_GetSearchCapabilities(self, *args, **kwargs): def soap_GetSearchCapabilities(self, *args, **kwargs):
"""Required: Return the searching capabilities supported by the device.""" """Required: Return the searching capabilities supported by the device."""


log.msg('GetSearchCapabilities()') log.msg('GetSearchCapabilities()')
return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }} return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }}


def soap_GetSortCapabilities(self, *args, **kwargs): def soap_GetSortCapabilities(self, *args, **kwargs):
"""Required: Return the CSV list of meta-data tags that can be used in """Required: Return the CSV list of meta-data tags that can be used in
sortCriteria.""" sortCriteria."""


log.msg('GetSortCapabilities()') log.msg('GetSortCapabilities()')
return { 'SortCapabilitiesResponse': { 'SortCaps': '' }} return { 'SortCapabilitiesResponse': { 'SortCaps': '' }}
def soap_GetSystemUpdateID(self, *args, **kwargs):
"""Required: Return the current value of state variable SystemUpdateID."""


log.msg('GetSystemUpdateID()') def soap_GetSystemUpdateID(self, *args, **kwargs):
self.needupdate = True """Required: Return the current value of state variable SystemUpdateID."""
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }}


BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren') log.msg('GetSystemUpdateID()')
self.needupdate = True
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }}


def soap_Browse(self, *args): BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren')
"""Required: Incrementally browse the native heirachy of the Content
Directory objects exposed by the Content Directory Service."""


(ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, def soap_Browse(self, *args):
SortCriteria) = args """Required: Incrementally browse the native heirachy of the Content
StartingIndex = int(StartingIndex) Directory objects exposed by the Content Directory Service."""
RequestedCount = int(RequestedCount)


log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, ' (ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount,
'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' % SortCriteria) = args
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`, StartingIndex = int(StartingIndex)
`RequestedCount`, `SortCriteria`)) RequestedCount = int(RequestedCount)


didl = DIDLElement() log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, '
result = {} 'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' %
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`,
`RequestedCount`, `SortCriteria`))


try: didl = DIDLElement()
if BrowseFlag == 'BrowseDirectChildren': result = {}
ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
filter(lambda x, s = self, d = didl: d.addItem(s[x.id]) and None, ch)
else:
didl.addItem(self[ObjectID])


result = {'BrowseResponse': {'Result': didl.toString() , try:
'NumberReturned': didl.numItems(), if BrowseFlag == 'BrowseDirectChildren':
'TotalMatches': didl.numItems(), ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
'UpdateID': 0}} filter(lambda x, s = self, d = didl: d.addItem(s[x.id]) and None, ch)
else:
didl.addItem(self[ObjectID])


except: result = {'BrowseResponse': {'Result': didl.toString() ,
traceback.print_exc(file=log.logfile) 'NumberReturned': didl.numItems(),
'TotalMatches': didl.numItems(),
'UpdateID': 0}}


log.msg('Returning: %s' % result) except:
traceback.print_exc(file=log.logfile)


return result log.msg('Returning: %s' % result)


# Optional actions return result


def soap_Search(self, *args, **kwargs): # Optional actions
"""Search for objects that match some search criteria."""


(ContainerID, SearchCriteria, Filter, StartingIndex, def soap_Search(self, *args, **kwargs):
RequestedCount, SortCriteria) = args """Search for objects that match some search criteria."""


log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \ (ContainerID, SearchCriteria, Filter, StartingIndex,
'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' % RequestedCount, SortCriteria) = args
(`ContainerID`, `SearchCriteria`, `Filter`,
`StartingIndex`, `RequestedCount`, `SortCriteria`))
def soap_CreateObject(self, *args, **kwargs):
"""Create a new object."""


(ContainerID, Elements) = args log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \
'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' %
(`ContainerID`, `SearchCriteria`, `Filter`,
`StartingIndex`, `RequestedCount`, `SortCriteria`))


log.msg('CreateObject(ContainerID=%s, Elements=%s)' % def soap_CreateObject(self, *args, **kwargs):
(`ContainerID`, `Elements`)) """Create a new object."""


def soap_DestroyObject(self, *args, **kwargs): (ContainerID, Elements) = args
"""Destroy the specified object."""


(ObjectID) = args log.msg('CreateObject(ContainerID=%s, Elements=%s)' %
(`ContainerID`, `Elements`))


log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`) def soap_DestroyObject(self, *args, **kwargs):
"""Destroy the specified object."""


def soap_UpdateObject(self, *args, **kwargs): (ObjectID) = args
"""Modify, delete or insert object metadata."""


(ObjectID, CurrentTagValue, NewTagValue) = args log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`)


log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \ def soap_UpdateObject(self, *args, **kwargs):
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`, """Modify, delete or insert object metadata."""
`NewTagValue`))


def soap_ImportResource(self, *args, **kwargs): (ObjectID, CurrentTagValue, NewTagValue) = args
"""Transfer a file from a remote source to a local
destination in the Content Directory Service."""


(SourceURI, DestinationURI) = args log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`,
`NewTagValue`))


log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' % def soap_ImportResource(self, *args, **kwargs):
(`SourceURI`, `DestinationURI`)) """Transfer a file from a remote source to a local
destination in the Content Directory Service."""


def soap_ExportResource(self, *args, **kwargs): (SourceURI, DestinationURI) = args
"""Transfer a file from a local source to a remote
destination."""


(SourceURI, DestinationURI) = args log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))


log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' % def soap_ExportResource(self, *args, **kwargs):
(`SourceURI`, `DestinationURI`)) """Transfer a file from a local source to a remote
destination."""


def soap_StopTransferResource(self, *args, **kwargs): (SourceURI, DestinationURI) = args
"""Stop a file transfer initiated by ImportResource or
ExportResource."""


(TransferID) = args log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' %
(`SourceURI`, `DestinationURI`))


log.msg('StopTransferResource(TransferID=%s)' % TransferID) def soap_StopTransferResource(self, *args, **kwargs):
"""Stop a file transfer initiated by ImportResource or
ExportResource."""


def soap_GetTransferProgress(self, *args, **kwargs): (TransferID) = args
"""Query the progress of a file transfer initiated by
an ImportResource or ExportResource action."""


(TransferID, TransferStatus, TransferLength, TransferTotal) = args log.msg('StopTransferResource(TransferID=%s)' % TransferID)


log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \ def soap_GetTransferProgress(self, *args, **kwargs):
'TransferLength=%s, TransferTotal=%s)' % """Query the progress of a file transfer initiated by
(`TransferId`, `TransferStatus`, `TransferLength`, an ImportResource or ExportResource action."""
`TransferTotal`))
def soap_DeleteResource(self, *args, **kwargs):
"""Delete a specified resource."""


(ResourceURI) = args (TransferID, TransferStatus, TransferLength, TransferTotal) = args


log.msg('DeleteResource(ResourceURI=%s)' % `ResourceURI`) log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \
'TransferLength=%s, TransferTotal=%s)' %
(`TransferId`, `TransferStatus`, `TransferLength`,
`TransferTotal`))


def soap_CreateReference(self, *args, **kwargs): def soap_DeleteResource(self, *args, **kwargs):
"""Create a reference to an existing object.""" """Delete a specified resource."""


(ContainerID, ObjectID) = args (ResourceURI) = args


log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' % log.msg('DeleteResource(ResourceURI=%s)' % `ResourceURI`)
(`ContainerID`, `ObjectID`)) def soap_CreateReference(self, *args, **kwargs):
"""Create a reference to an existing object."""

(ContainerID, ObjectID) = args

log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' %
(`ContainerID`, `ObjectID`))


class ContentDirectoryServer(resource.Resource): class ContentDirectoryServer(resource.Resource):
def __init__(self, title): def __init__(self, title):
resource.Resource.__init__(self) resource.Resource.__init__(self)
self.putChild('scpd.xml', static.File('content-directory-scpd.xml')) self.putChild('scpd.xml', static.File('content-directory-scpd.xml'))
self.control = ContentDirectoryControl(title) self.control = ContentDirectoryControl(title)
self.putChild('control', self.control) self.putChild('control', self.control)

+ 213
- 213
DIDLLite.py View File

@@ -6,346 +6,346 @@
from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface


class Resource: class Resource:
"""An object representing a resource.""" """An object representing a resource."""


def __init__(self, data, protocolInfo): def __init__(self, data, protocolInfo):
self.data = data self.data = data
self.protocolInfo = protocolInfo self.protocolInfo = protocolInfo
self.bitrate = None self.bitrate = None
self.size = None self.size = None
def toElement(self):


root = Element('res') def toElement(self):
root.attrib['protocolInfo'] = self.protocolInfo
root.text = self.data


if self.bitrate is not None: root = Element('res')
root.attrib['bitrate'] = str(self.bitrate) root.attrib['protocolInfo'] = self.protocolInfo
root.text = self.data


if self.size is not None: if self.bitrate is not None:
root.attrib['size'] = str(self.size) root.attrib['bitrate'] = str(self.bitrate)


return root if self.size is not None:
root.attrib['size'] = str(self.size)

return root


class Object: class Object:
"""The root class of the entire content directory class heirachy.""" """The root class of the entire content directory class heirachy."""

klass = 'object'
creator = None
res = None
writeStatus = None


klass = 'object' def __init__(self, cd, id, parentID, title, restricted = False,
creator = None creator = None):
res = None
writeStatus = None


def __init__(self, cd, id, parentID, title, restricted = False, self.cd = cd
creator = None): self.id = id
self.parentID = parentID
self.title = title
self.creator = creator


self.cd = cd if restricted:
self.id = id self.restricted = '1'
self.parentID = parentID else:
self.title = title self.restricted = '0'
self.creator = creator
if restricted:
self.restricted = '1'
else:
self.restricted = '0'


def toElement(self): def toElement(self):


root = Element(self.elementName) root = Element(self.elementName)


root.attrib['id'] = self.id root.attrib['id'] = self.id
root.attrib['parentID'] = self.parentID root.attrib['parentID'] = self.parentID
SubElement(root, 'dc:title').text = self.title SubElement(root, 'dc:title').text = self.title
SubElement(root, 'upnp:class').text = self.klass SubElement(root, 'upnp:class').text = self.klass


root.attrib['restricted'] = self.restricted root.attrib['restricted'] = self.restricted


if self.creator is not None: if self.creator is not None:
SubElement(root, 'dc:creator').text = self.creator SubElement(root, 'dc:creator').text = self.creator


if self.res is not None: if self.res is not None:
root.append(self.res.toElement()) root.append(self.res.toElement())


if self.writeStatus is not None: if self.writeStatus is not None:
SubElement(root, 'upnp:writeStatus').text = self.writeStatus SubElement(root, 'upnp:writeStatus').text = self.writeStatus


return root return root
def toString(self):
def toString(self): return tostring(self.toElement())
return tostring(self.toElement())


class Item(Object): class Item(Object):
"""A class used to represent atomic (non-container) content """A class used to represent atomic (non-container) content
objects.""" objects."""


klass = Object.klass + '.item' klass = Object.klass + '.item'
elementName = 'item' elementName = 'item'
refID = None refID = None


def toElement(self): def toElement(self):


root = Object.toElement(self) root = Object.toElement(self)


if self.refID is not None: if self.refID is not None:
SubElement(root, 'refID').text = self.refID SubElement(root, 'refID').text = self.refID


return root return root


class ImageItem(Item): class ImageItem(Item):
klass = Item.klass + '.imageItem' klass = Item.klass + '.imageItem'


class Photo(ImageItem): class Photo(ImageItem):
klass = ImageItem.klass + '.photo' klass = ImageItem.klass + '.photo'


class AudioItem(Item): class AudioItem(Item):
"""A piece of content that when rendered generates some audio.""" """A piece of content that when rendered generates some audio."""

klass = Item.klass + '.audioItem'


klass = Item.klass + '.audioItem' genre = None
description = None
longDescription = None
publisher = None
language = None
relation = None
rights = None


genre = None def toElement(self):
description = None
longDescription = None
publisher = None
language = None
relation = None
rights = None


def toElement(self): root = Item.toElement(self)


root = Item.toElement(self) if self.genre is not None:
SubElement(root, 'upnp:genre').text = self.genre
if self.genre is not None:
SubElement(root, 'upnp:genre').text = self.genre


if self.description is not None: if self.description is not None:
SubElement(root, 'dc:description').text = self.description SubElement(root, 'dc:description').text = self.description


if self.longDescription is not None: if self.longDescription is not None:
SubElement(root, 'upnp:longDescription').text = \ SubElement(root, 'upnp:longDescription').text = \
self.longDescription self.longDescription


if self.publisher is not None: if self.publisher is not None:
SubElement(root, 'dc:publisher').text = self.publisher SubElement(root, 'dc:publisher').text = self.publisher


if self.language is not None: if self.language is not None:
SubElement(root, 'dc:language').text = self.language SubElement(root, 'dc:language').text = self.language


if self.relation is not None: if self.relation is not None:
SubElement(root, 'dc:relation').text = self.relation SubElement(root, 'dc:relation').text = self.relation


if self.rights is not None: if self.rights is not None:
SubElement(root, 'dc:rights').text = self.rights SubElement(root, 'dc:rights').text = self.rights


return root return root


class MusicTrack(AudioItem): class MusicTrack(AudioItem):
"""A discrete piece of audio that should be interpreted as music.""" """A discrete piece of audio that should be interpreted as music."""


klass = AudioItem.klass + '.musicTrack' klass = AudioItem.klass + '.musicTrack'


artist = None artist = None
album = None album = None
originalTrackNumber = None originalTrackNumber = None
playlist = None playlist = None
storageMedium = None storageMedium = None
contributor = None contributor = None
date = None date = None


def toElement(self): def toElement(self):


root = AudioItem.toElement(self) root = AudioItem.toElement(self)


if self.artist is not None: if self.artist is not None:
SubElement(root, 'upnp:artist').text = self.artist SubElement(root, 'upnp:artist').text = self.artist


if self.album is not None: if self.album is not None:
SubElement(root, 'upnp:album').text = self.album SubElement(root, 'upnp:album').text = self.album


if self.originalTrackNumber is not None: if self.originalTrackNumber is not None:
SubElement(root, 'upnp:originalTrackNumber').text = \ SubElement(root, 'upnp:originalTrackNumber').text = \
self.originalTrackNumber self.originalTrackNumber


if self.playlist is not None: if self.playlist is not None:
SubElement(root, 'upnp:playlist').text = self.playlist SubElement(root, 'upnp:playlist').text = self.playlist


if self.storageMedium is not None: if self.storageMedium is not None:
SubElement(root, 'upnp:storageMedium').text = self.storageMedium SubElement(root, 'upnp:storageMedium').text = self.storageMedium


if self.contributor is not None: if self.contributor is not None:
SubElement(root, 'dc:contributor').text = self.contributor SubElement(root, 'dc:contributor').text = self.contributor


if self.date is not None: if self.date is not None:
SubElement(root, 'dc:date').text = self.date SubElement(root, 'dc:date').text = self.date


return root return root


class AudioBroadcast(AudioItem): class AudioBroadcast(AudioItem):
klass = AudioItem.klass + '.audioBroadcast' klass = AudioItem.klass + '.audioBroadcast'


class AudioBook(AudioItem): class AudioBook(AudioItem):
klass = AudioItem.klass + '.audioBook' klass = AudioItem.klass + '.audioBook'


class VideoItem(Item): class VideoItem(Item):
klass = Item.klass + '.videoItem' klass = Item.klass + '.videoItem'


class Movie(VideoItem): class Movie(VideoItem):
klass = VideoItem.klass + '.movie' klass = VideoItem.klass + '.movie'


class VideoBroadcast(VideoItem): class VideoBroadcast(VideoItem):
klass = VideoItem.klass + '.videoBroadcast' klass = VideoItem.klass + '.videoBroadcast'


class MusicVideoClip(VideoItem): class MusicVideoClip(VideoItem):
klass = VideoItem.klass + '.musicVideoClip' klass = VideoItem.klass + '.musicVideoClip'


class PlaylistItem(Item): class PlaylistItem(Item):
klass = Item.klass + '.playlistItem' klass = Item.klass + '.playlistItem'


class TextItem(Item): class TextItem(Item):
klass = Item.klass + '.textItem' klass = Item.klass + '.textItem'


class Container(Object, list): class Container(Object, list):
"""An object that can contain other objects.""" """An object that can contain other objects."""


klass = Object.klass + '.container' klass = Object.klass + '.container'


elementName = 'container' elementName = 'container'
childCount = property(lambda x: len(x)) childCount = property(lambda x: len(x))
createClass = None createClass = None
searchClass = None searchClass = None
searchable = None searchable = None


def __init__(self, cd, id, parentID, title, restricted = 0, creator = None): def __init__(self, cd, id, parentID, title, restricted = 0, creator = None):
Object.__init__(self, cd, id, parentID, title, restricted, creator) Object.__init__(self, cd, id, parentID, title, restricted, creator)
list.__init__(self) list.__init__(self)


def toElement(self): def toElement(self):


root = Object.toElement(self) root = Object.toElement(self)


root.attrib['childCount'] = str(self.childCount) root.attrib['childCount'] = str(self.childCount)


if self.createClass is not None: if self.createClass is not None:
SubElement(root, 'upnp:createclass').text = self.createClass SubElement(root, 'upnp:createclass').text = self.createClass
if self.searchClass is not None:
if not isinstance(self.searchClass, (list, tuple)):
self.searchClass = ['searchClass']
for i in searchClass:
SubElement(root, 'upnp:searchclass').text = i


if self.searchable is not None: if self.searchClass is not None:
root.attrib['searchable'] = str(self.searchable) if not isinstance(self.searchClass, (list, tuple)):
self.searchClass = ['searchClass']
for i in searchClass:
SubElement(root, 'upnp:searchclass').text = i


return root if self.searchable is not None:
root.attrib['searchable'] = str(self.searchable)

return root


class Person(Container): class Person(Container):
klass = Container.klass + '.person' klass = Container.klass + '.person'


class MusicArtist(Person): class MusicArtist(Person):
klass = Person.klass + '.musicArtist' klass = Person.klass + '.musicArtist'


class PlaylistContainer(Container): class PlaylistContainer(Container):
klass = Container.klass + '.playlistContainer' klass = Container.klass + '.playlistContainer'


class Album(Container): class Album(Container):
klass = Container.klass + '.album' klass = Container.klass + '.album'


class MusicAlbum(Album): class MusicAlbum(Album):
klass = Album.klass + '.musicAlbum' klass = Album.klass + '.musicAlbum'


class PhotoAlbum(Album): class PhotoAlbum(Album):
klass = Album.klass + '.photoAlbum' klass = Album.klass + '.photoAlbum'


class Genre(Container): class Genre(Container):
klass = Container.klass + '.genre' klass = Container.klass + '.genre'


class MusicGenre(Genre): class MusicGenre(Genre):
klass = Genre.klass + '.musicGenre' klass = Genre.klass + '.musicGenre'


class MovieGenre(Genre): class MovieGenre(Genre):
klass = Genre.klass + '.movieGenre' klass = Genre.klass + '.movieGenre'


class StorageSystem(Container): class StorageSystem(Container):
klass = Container.klass + '.storageSystem' klass = Container.klass + '.storageSystem'

total = -1
used = -1
free = -1
maxpartition = -1
medium = 'UNKNOWN'


total = -1 def toElement(self):
used = -1
free = -1
maxpartition = -1
medium = 'UNKNOWN'


def toElement(self): root = Container.toElement(self)


root = Container.toElement(self) SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageTotal').text = str(self.total) SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageUsed').text = str(self.used) SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
SubElement(root, 'upnp:storageFree').text = str(self.free) SubElement(root, 'upnp:storageMedium').text = self.medium
SubElement(root, 'upnp:storageMaxPartition').text = str(self.maxpartition)
SubElement(root, 'upnp:storageMedium').text = self.medium


return root return root


class StorageVolume(Container): class StorageVolume(Container):
klass = Container.klass + '.storageVolume' klass = Container.klass + '.storageVolume'


total = -1 total = -1
used = -1 used = -1
free = -1 free = -1
medium = 'UNKNOWN' medium = 'UNKNOWN'


def toElement(self): def toElement(self):


root = Container.toElement(self) root = Container.toElement(self)
SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMedium').text = self.medium


return root SubElement(root, 'upnp:storageTotal').text = str(self.total)
SubElement(root, 'upnp:storageUsed').text = str(self.used)
SubElement(root, 'upnp:storageFree').text = str(self.free)
SubElement(root, 'upnp:storageMedium').text = self.medium

return root


class StorageFolder(Container): class StorageFolder(Container):
klass = Container.klass + '.storageFolder' klass = Container.klass + '.storageFolder'

used = -1


used = -1 def toElement(self):


def toElement(self): root = Container.toElement(self)


root = Container.toElement(self) if self.used is not None:
SubElement(root, 'upnp:storageUsed').text = str(self.used)
if self.used is not None:
SubElement(root, 'upnp:storageUsed').text = str(self.used)


return root return root


class DIDLElement(_ElementInterface): class DIDLElement(_ElementInterface):
def __init__(self): def __init__(self):
_ElementInterface.__init__(self, 'DIDL-Lite', {}) _ElementInterface.__init__(self, 'DIDL-Lite', {})
self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite' self.attrib['xmlns'] = 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite'
self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/' self.attrib['xmlns:dc'] = 'http://purl.org/dc/elements/1.1/'
self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp' self.attrib['xmlns:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp'


def addContainer(self, id, parentID, title, restricted = False): def addContainer(self, id, parentID, title, restricted = False):
e = Container(id, parentID, title, restricted, creator = '') e = Container(id, parentID, title, restricted, creator = '')
self.append(e.toElement()) self.append(e.toElement())


def addItem(self, item): def addItem(self, item):
self.append(item.toElement()) self.append(item.toElement())


def numItems(self): def numItems(self):
return len(self) return len(self)


def toString(self): def toString(self):
return tostring(self) return tostring(self)


if __name__ == '__main__': if __name__ == '__main__':


root = DIDLElement() root = DIDLElement()
root.addContainer('0\Movie\\', '0\\', 'Movie') root.addContainer('0\Movie\\', '0\\', 'Movie')
root.addContainer('0\Music\\', '0\\', 'Music') root.addContainer('0\Music\\', '0\\', 'Music')
root.addContainer('0\Photo\\', '0\\', 'Photo') root.addContainer('0\Photo\\', '0\\', 'Photo')
root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia') root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia')


print tostring(root) print tostring(root)

+ 148
- 160
SSDP.py View File

@@ -26,167 +26,155 @@ SSDP_ADDR = '239.255.255.250'
# class to handle services etc. # class to handle services etc.


class SSDPServer(DatagramProtocol): class SSDPServer(DatagramProtocol):
"""A class implementing a SSDP server. The notifyReceived and """A class implementing a SSDP server. The notifyReceived and
searchReceived methods are called when the appropriate type of searchReceived methods are called when the appropriate type of
datagram is received by the server.""" datagram is received by the server."""


# not used yet # not used yet
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ] stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ]
elements = {} elements = {}
known = {} known = {}


def doStop(self): def doStop(self):
'''Make sure we send out the byebye notifications.''' '''Make sure we send out the byebye notifications.'''


self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT)) self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT))
for st in self.known: for st in self.known:
self.doByebye(st) self.doByebye(st)
DatagramProtocol.doStop(self) DatagramProtocol.doStop(self)

def datagramReceived(self, data, (host, port)):
def datagramReceived(self, data, (host, port)): """Handle a received multicast datagram."""
"""Handle a received multicast datagram.""" # Break up message in to command and headers

# TODO: use the email module after trimming off the request line..
# Break up message in to command and headers # This gets us much better header support.
# TODO: use the email module after trimming off the request line.. header, payload = data.split('\r\n\r\n')
# This gets us much better header support. lines = header.split('\r\n')

cmd = string.split(lines[0], ' ')
header, payload = data.split('\r\n\r\n') lines = map(lambda x: x.replace(': ', ':', 1), lines[1:])
lines = header.split('\r\n') lines = filter(lambda x: len(x) > 0, lines)
cmd = string.split(lines[0], ' ') headers = [string.split(x, ':', 1) for x in lines]
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:]) headers = dict(map(lambda x: (x[0].lower(), x[1]), headers))
lines = filter(lambda x: len(x) > 0, lines) if cmd[0] == 'M-SEARCH' and cmd[1] == '*':

# SSDP discovery
headers = [string.split(x, ':', 1) for x in lines] self.discoveryRequest(headers, (host, port))
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) elif cmd[0] == 'NOTIFY' and cmd[1] == '*':

# SSDP presence
if cmd[0] == 'M-SEARCH' and cmd[1] == '*': self.notifyReceived(headers, (host, port))
# SSDP discovery else:
self.discoveryRequest(headers, (host, port)) log.msg('Unknown SSDP command %s %s' % cmd)
elif cmd[0] == 'NOTIFY' and cmd[1] == '*': def discoveryRequest(self, headers, (host, port)):
# SSDP presence """Process a discovery request. The response must be sent to
self.notifyReceived(headers, (host, port)) the address specified by (host, port)."""
else: log.msg('Discovery request for %s' % headers['st'])
log.msg('Unknown SSDP command %s %s' % cmd) # Do we know about this service?

if headers['st'] == 'ssdp:all':
def discoveryRequest(self, headers, (host, port)): for i in self.known:
"""Process a discovery request. The response must be sent to hcopy = dict(headers.iteritems())
the address specified by (host, port).""" hcopy['st'] = i

self.discoveryRequest(hcopy, (host, post))
log.msg('Discovery request for %s' % headers['st']) return

if not self.known.has_key(headers['st']):
# Do we know about this service? return

# Generate a response
if headers['st'] == 'ssdp:all': response = []
for i in self.known: response.append('HTTP/1.1 200 OK')
hcopy = dict(headers.iteritems()) for k, v in self.known[headers['st']].items():
hcopy['st'] = i response.append('%s: %s' % (k, v))
self.discoveryRequest(hcopy, (host, post)) response.extend(('', ''))
return delay = random.randint(0, int(headers['mx']))
if not self.known.has_key(headers['st']): reactor.callLater(delay, self.transport.write,
return '\r\n'.join(response), (host, port))

def register(self, usn, st, location):
# Generate a response """Register a service or device that this SSDP server will

respond to."""
response = [] log.msg('Registering %s' % st)
response.append('HTTP/1.1 200 OK') self.known[st] = {}

self.known[st]['USN'] = usn
for k, v in self.known[headers['st']].items(): self.known[st]['LOCATION'] = location
response.append('%s: %s' % (k, v)) self.known[st]['ST'] = st

self.known[st]['EXT'] = ''
response.extend(('', '')) self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp'
delay = random.randint(0, int(headers['mx'])) self.known[st]['CACHE-CONTROL'] = 'max-age=1800'
reactor.callLater(delay, self.transport.write, self.doNotify(st)
'\r\n'.join(response), (host, port)) def doByebye(self, st):

"""Do byebye"""
def register(self, usn, st, location): log.msg('Sending byebye notification for %s' % st)
"""Register a service or device that this SSDP server will resp = [ 'NOTIFY * HTTP/1.1',
respond to.""" 'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT),

'NTS: ssdp:byebye',
log.msg('Registering %s' % st) ]
stcpy = dict(self.known[st].iteritems())
self.known[st] = {} stcpy['NT'] = stcpy['ST']
self.known[st]['USN'] = usn del stcpy['ST']
self.known[st]['LOCATION'] = location resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
self.known[st]['ST'] = st resp.extend(('', ''))
self.known[st]['EXT'] = '' self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp' def doNotify(self, st):
self.known[st]['CACHE-CONTROL'] = 'max-age=1800' """Do notification"""
self.doNotify(st) log.msg('Sending alive notification for %s' % st)

resp = [ 'NOTIFY * HTTP/1.1',
def doByebye(self, st): 'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT),
"""Do byebye""" 'NTS: ssdp:alive',

]
log.msg('Sending byebye notification for %s' % st) stcpy = dict(self.known[st].iteritems())

stcpy['NT'] = stcpy['ST']
resp = [ 'NOTIFY * HTTP/1.1', del stcpy['ST']
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems()))
'NTS: ssdp:byebye', resp.extend(('', ''))
] self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT))
stcpy = dict(self.known[st].iteritems()) def notifyReceived(self, headers, (host, port)):
stcpy['NT'] = stcpy['ST'] """Process a presence announcement. We just remember the
del stcpy['ST'] details of the SSDP service announced."""
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) if headers['nts'] == 'ssdp:alive':
resp.extend(('', '')) if not self.elements.has_key(headers['nt']):
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) # Register device/service

self.elements[headers['nt']] = {}
def doNotify(self, st): self.elements[headers['nt']]['USN'] = headers['usn']
"""Do notification""" self.elements[headers['nt']]['host'] = (host, port)

log.msg('Detected presence of %s' % headers['nt'])
log.msg('Sending alive notification for %s' % st) elif headers['nts'] == 'ssdp:byebye':

if self.elements.has_key(headers['nt']):
resp = [ 'NOTIFY * HTTP/1.1', # Unregister device/service
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), del(self.elements[headers['nt']])
'NTS: ssdp:alive', log.msg('Detected absence for %s' % headers['nt'])
] else:
stcpy = dict(self.known[st].iteritems()) log.msg('Unknown subtype %s for notification type %s' %
stcpy['NT'] = stcpy['ST'] (headers['nts'], headers['nt']))
del stcpy['ST'] def findService(self, name):
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) """Return information about a service registered over SSDP."""
resp.extend(('', '')) # TODO: Implement me.
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) # TODO: Send out a discovery request if we haven't registered

# a presence announcement.
def notifyReceived(self, headers, (host, port)): def findDevice(self, name):
"""Process a presence announcement. We just remember the """Return information about a device registered over SSDP."""
details of the SSDP service announced.""" # TODO: Implement me.

# TODO: Send out a discovery request if we haven't registered
if headers['nts'] == 'ssdp:alive': # a presence announcement.

if not self.elements.has_key(headers['nt']):

# Register device/service

self.elements[headers['nt']] = {}
self.elements[headers['nt']]['USN'] = headers['usn']
self.elements[headers['nt']]['host'] = (host, port)

log.msg('Detected presence of %s' % headers['nt'])

elif headers['nts'] == 'ssdp:byebye':

if self.elements.has_key(headers['nt']):

# Unregister device/service
del(self.elements[headers['nt']])

log.msg('Detected absence for %s' % headers['nt'])
else:
log.msg('Unknown subtype %s for notification type %s' %
(headers['nts'], headers['nt']))

def findService(self, name):
"""Return information about a service registered over SSDP."""

# TODO: Implement me.

# TODO: Send out a discovery request if we haven't registered
# a presence announcement.

def findDevice(self, name):
"""Return information about a device registered over SSDP."""

# TODO: Implement me.

# TODO: Send out a discovery request if we haven't registered
# a presence announcement.

+ 9
- 9
browse-zms.py View File

@@ -12,8 +12,8 @@ from twisted.internet import reactor, error
from twisted.internet.protocol import Protocol, ClientFactory from twisted.internet.protocol import Protocol, ClientFactory


class Send(Protocol): class Send(Protocol):
def connectionMade(self): def connectionMade(self):
self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r
Host: 192.168.126.1:80\r Host: 192.168.126.1:80\r
User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r
@@ -35,16 +35,16 @@ Content-Length: 511\r
</s:Body>\r </s:Body>\r
</s:Envelope>\r\n''') </s:Envelope>\r\n''')


def dataReceived(self, data): def dataReceived(self, data):
print(data) print(data)


def connectionLost(self, reason): def connectionLost(self, reason):
if reason.type != error.ConnectionDone: if reason.type != error.ConnectionDone:
print str(reason) print str(reason)
reactor.stop() reactor.stop()


class SendFactory(ClientFactory): class SendFactory(ClientFactory):
protocol = Send protocol = Send


host = '192.168.126.128' host = '192.168.126.128'
port = 5643 port = 5643


+ 28
- 28
cdsclient View File

@@ -15,38 +15,38 @@ from twisted.python import usage
import sys, string, SOAPpy import sys, string, SOAPpy


class UPnPSOAPProxy: class UPnPSOAPProxy:
"""A proxy for making UPnP SOAP calls.""" """A proxy for making UPnP SOAP calls."""


def __init__(self, url): def __init__(self, url):
self.url = url self.url = url


def _cbGotResult(self, result): def _cbGotResult(self, result):
return SOAPpy.parseSOAPRPC(result) return SOAPpy.parseSOAPRPC(result)


def callRemote(self, method, *args, **kwargs): def callRemote(self, method, *args, **kwargs):


payload = SOAPpy.buildSOAP(args = args, kw = kwargs, method = method) payload = SOAPpy.buildSOAP(args = args, kw = kwargs, method = method)
payload = string.replace(payload, '\n', '\r\n') payload = string.replace(payload, '\n', '\r\n')
action = \
action = \ '"urn:schemas-upnp-org:service:ContentDirectory:1#%s"' % method
'"urn:schemas-upnp-org:service:ContentDirectory:1#%s"' % method page = client.getPage(self.url, postdata = payload,
method = 'POST', headers = {'Content-Type': 'text/xml',
page = client.getPage(self.url, postdata = payload, method = 'POST', 'SOAPACTION': action})
headers = {'Content-Type': 'text/xml',
'SOAPACTION': action})


return page.addCallback(self._cbGotResult) return page.addCallback(self._cbGotResult)


class Options(usage.Options): class Options(usage.Options):
pass pass


def printResult(value): def printResult(value):
print value print value
reactor.stop() reactor.stop()


def printError(error): def printError(error):
print 'error', error print 'error', error
reactor.stop() reactor.stop()


#proxy = UPnPSOAPProxy('http://192.168.126.128:5643/ContentDirectory/control') #proxy = UPnPSOAPProxy('http://192.168.126.128:5643/ContentDirectory/control')
proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control') proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control')
@@ -56,12 +56,12 @@ proxy = UPnPSOAPProxy('http://127.0.0.1:8080/ContentDirectory/control')
#proxy.callRemote('GetSortCapabilities').addCallbacks(printResult, printError) #proxy.callRemote('GetSortCapabilities').addCallbacks(printResult, printError)


proxy.callRemote('Browse', proxy.callRemote('Browse',
ObjectID = '0\\Music\\Genres\\Others\\chimes.wav', ObjectID = '0\\Music\\Genres\\Others\\chimes.wav',
# BrowseFlag = 'BrowseDirectChildren', #BrowseFlag = 'BrowseDirectChildren',
BrowseFlag = 'BrowseMetadata', BrowseFlag = 'BrowseMetadata',
Filter = '*', Filter = '*',
StartingIndex = 0, StartingIndex = 0,
RequestedCount = 700, RequestedCount = 700,
SortCriteria = None).addCallbacks(printResult, printError) SortCriteria = None).addCallbacks(printResult, printError)


reactor.run() reactor.run()

+ 5
- 7
upnp.py View File

@@ -8,11 +8,9 @@ from twisted.web import soap
import SOAPpy import SOAPpy


class UPnPPublisher(soap.SOAPPublisher): class UPnPPublisher(soap.SOAPPublisher):
"""UPnP requires OUT parameters to be returned in a slightly """UPnP requires OUT parameters to be returned in a slightly
different way than the SOAPPublisher class does.""" different way than the SOAPPublisher class does."""

def _gotResult(self, result, request, methodName):
response = SOAPpy.buildSOAP(kw=result,
encoding=self.encoding)
self._sendResponse(request, response)


def _gotResult(self, result, request, methodName):
response = SOAPpy.buildSOAP(kw=result, encoding=self.encoding)
self._sendResponse(request, response)

+ 1
- 1
xmlpretty View File

@@ -12,7 +12,7 @@ from xml.dom import minidom


str = "" str = ""
for s in sys.stdin.readlines(): for s in sys.stdin.readlines():
str = str + s[:-1] # Eat trailing \n str = str + s[:-1] # Eat trailing \n


doc = minidom.parseString(str) doc = minidom.parseString(str)
print doc.toprettyxml(indent = " ") print doc.toprettyxml(indent = " ")

||||||
x
 
000:0
Loading…
Cancel
Save