[git-p4: depot-paths = "//depot/": change = 763]replace/4e84fdb41ea781c7a8f872baa423e8b3be4045a7
@@ -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()) |
@@ -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) |
@@ -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) |
@@ -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. |
@@ -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 | ||||
@@ -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() |
@@ -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) |
@@ -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 = " ") |