[git-p4: depot-paths = "//depot/": change = 763]replace/4e84fdb41ea781c7a8f872baa423e8b3be4045a7
@@ -10,10 +10,10 @@ from twisted.web import resource, static, soap | |||
from upnp import UPnPPublisher | |||
class ConnectionManagerControl(UPnPPublisher): | |||
pass | |||
pass | |||
class ConnectionManagerServer(resource.Resource): | |||
def __init__(self): | |||
resource.Resource.__init__(self) | |||
self.putChild('scpd.xml', static.File('connection-manager-scpd.xml')) | |||
self.putChild('control', ConnectionManagerControl()) | |||
def __init__(self): | |||
resource.Resource.__init__(self) | |||
self.putChild('scpd.xml', static.File('connection-manager-scpd.xml')) | |||
self.putChild('control', ConnectionManagerControl()) |
@@ -14,9 +14,9 @@ | |||
# twisted swallows the tracebacks. At the moment I'm going: | |||
# | |||
# try: | |||
# .... | |||
# .... | |||
# except: | |||
# traceback.print_exc(file = log.logfile) | |||
# traceback.print_exc(file = log.logfile) | |||
# | |||
from twisted.python import log | |||
@@ -30,208 +30,208 @@ import traceback | |||
from urllib import quote | |||
class ContentDirectoryControl(UPnPPublisher, dict): | |||
"""This class implements the CDS actions over SOAP.""" | |||
def getnextID(self): | |||
ret = str(self.nextID) | |||
self.nextID += 1 | |||
return ret | |||
def addContainer(self, parent, title, klass = Container, **kwargs): | |||
ret = self.addItem(parent, klass, title, **kwargs) | |||
self.children[ret] = self[ret] | |||
return ret | |||
def addItem(self, parent, klass, *args, **kwargs): | |||
assert isinstance(self[parent], Container) | |||
nid = self.getnextID() | |||
i = klass(self, nid, parent, *args, **kwargs) | |||
self.children[parent].append(i) | |||
self[i.id] = i | |||
return i.id | |||
def delItem(self, id): | |||
"""This class implements the CDS actions over SOAP.""" | |||
def getnextID(self): | |||
ret = str(self.nextID) | |||
self.nextID += 1 | |||
return ret | |||
def addContainer(self, parent, title, klass = Container, **kwargs): | |||
ret = self.addItem(parent, klass, title, **kwargs) | |||
self.children[ret] = self[ret] | |||
return ret | |||
def addItem(self, parent, klass, *args, **kwargs): | |||
assert isinstance(self[parent], Container) | |||
nid = self.getnextID() | |||
i = klass(self, nid, parent, *args, **kwargs) | |||
self.children[parent].append(i) | |||
self[i.id] = i | |||
return i.id | |||
def delItem(self, id): | |||
if isinstance(self[id], Container): | |||
for i in self.children[id]: | |||
self.delItem(i) | |||
assert len(self.children[id]) == 0 | |||
del self.children[id] | |||
del self[id] | |||
del self[id] | |||
def getchildren(self, item): | |||
assert isinstance(self[item], Container) | |||
return self.children[item][:] | |||
def getchildren(self, item): | |||
assert isinstance(self[item], Container) | |||
return self.children[item][:] | |||
def __init__(self, title, *args): | |||
super(ContentDirectoryControl, self).__init__(*args) | |||
fakeparent = '-1' | |||
self.nextID = 0 | |||
self.children = { fakeparent: []} | |||
self.needupdate = False | |||
self.updateId = 0 | |||
self[fakeparent] = Container(None, '-1', 'fake') | |||
root = self.addContainer(fakeparent, title) | |||
assert root == '0' | |||
del self[fakeparent] | |||
del self.children[fakeparent] | |||
def __init__(self, title, *args): | |||
super(ContentDirectoryControl, self).__init__(*args) | |||
fakeparent = '-1' | |||
self.nextID = 0 | |||
self.children = { fakeparent: []} | |||
self.needupdate = False | |||
self.updateId = 0 | |||
self[fakeparent] = Container(None, '-1', 'fake') | |||
root = self.addContainer(fakeparent, title) | |||
assert root == '0' | |||
del self[fakeparent] | |||
del self.children[fakeparent] | |||
def doupdate(self): | |||
if self.needupdate: | |||
self.needupdate += 1 | |||
self.needupdate = False | |||
def doupdate(self): | |||
if self.needupdate: | |||
self.needupdate += 1 | |||
self.needupdate = False | |||
# Required actions | |||
# Required actions | |||
def soap_GetSearchCapabilities(self, *args, **kwargs): | |||
"""Required: Return the searching capabilities supported by the device.""" | |||
def soap_GetSearchCapabilities(self, *args, **kwargs): | |||
"""Required: Return the searching capabilities supported by the device.""" | |||
log.msg('GetSearchCapabilities()') | |||
return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }} | |||
log.msg('GetSearchCapabilities()') | |||
return { 'SearchCapabilitiesResponse': { 'SearchCaps': '' }} | |||
def soap_GetSortCapabilities(self, *args, **kwargs): | |||
"""Required: Return the CSV list of meta-data tags that can be used in | |||
sortCriteria.""" | |||
def soap_GetSortCapabilities(self, *args, **kwargs): | |||
"""Required: Return the CSV list of meta-data tags that can be used in | |||
sortCriteria.""" | |||
log.msg('GetSortCapabilities()') | |||
return { 'SortCapabilitiesResponse': { 'SortCaps': '' }} | |||
def soap_GetSystemUpdateID(self, *args, **kwargs): | |||
"""Required: Return the current value of state variable SystemUpdateID.""" | |||
log.msg('GetSortCapabilities()') | |||
return { 'SortCapabilitiesResponse': { 'SortCaps': '' }} | |||
log.msg('GetSystemUpdateID()') | |||
self.needupdate = True | |||
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }} | |||
def soap_GetSystemUpdateID(self, *args, **kwargs): | |||
"""Required: Return the current value of state variable SystemUpdateID.""" | |||
BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren') | |||
log.msg('GetSystemUpdateID()') | |||
self.needupdate = True | |||
return { 'SystemUpdateIdResponse': { 'Id': self.updateId }} | |||
def soap_Browse(self, *args): | |||
"""Required: Incrementally browse the native heirachy of the Content | |||
Directory objects exposed by the Content Directory Service.""" | |||
BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren') | |||
(ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, | |||
SortCriteria) = args | |||
StartingIndex = int(StartingIndex) | |||
RequestedCount = int(RequestedCount) | |||
def soap_Browse(self, *args): | |||
"""Required: Incrementally browse the native heirachy of the Content | |||
Directory objects exposed by the Content Directory Service.""" | |||
log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, ' | |||
'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' % | |||
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`, | |||
`RequestedCount`, `SortCriteria`)) | |||
(ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, | |||
SortCriteria) = args | |||
StartingIndex = int(StartingIndex) | |||
RequestedCount = int(RequestedCount) | |||
didl = DIDLElement() | |||
result = {} | |||
log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, ' | |||
'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' % | |||
(`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`, | |||
`RequestedCount`, `SortCriteria`)) | |||
try: | |||
if BrowseFlag == 'BrowseDirectChildren': | |||
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]) | |||
didl = DIDLElement() | |||
result = {} | |||
result = {'BrowseResponse': {'Result': didl.toString() , | |||
'NumberReturned': didl.numItems(), | |||
'TotalMatches': didl.numItems(), | |||
'UpdateID': 0}} | |||
try: | |||
if BrowseFlag == 'BrowseDirectChildren': | |||
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]) | |||
except: | |||
traceback.print_exc(file=log.logfile) | |||
result = {'BrowseResponse': {'Result': didl.toString() , | |||
'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): | |||
"""Search for objects that match some search criteria.""" | |||
# Optional actions | |||
(ContainerID, SearchCriteria, Filter, StartingIndex, | |||
RequestedCount, SortCriteria) = args | |||
def soap_Search(self, *args, **kwargs): | |||
"""Search for objects that match some search criteria.""" | |||
log.msg('Search(ContainerID=%s, SearchCriteria=%s, Filter=%s, ' \ | |||
'StartingIndex=%s, RequestedCount=%s, SortCriteria=%s)' % | |||
(`ContainerID`, `SearchCriteria`, `Filter`, | |||
`StartingIndex`, `RequestedCount`, `SortCriteria`)) | |||
def soap_CreateObject(self, *args, **kwargs): | |||
"""Create a new object.""" | |||
(ContainerID, SearchCriteria, Filter, StartingIndex, | |||
RequestedCount, SortCriteria) = args | |||
(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)' % | |||
(`ContainerID`, `Elements`)) | |||
def soap_CreateObject(self, *args, **kwargs): | |||
"""Create a new object.""" | |||
def soap_DestroyObject(self, *args, **kwargs): | |||
"""Destroy the specified object.""" | |||
(ContainerID, Elements) = args | |||
(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): | |||
"""Modify, delete or insert object metadata.""" | |||
(ObjectID) = args | |||
(ObjectID, CurrentTagValue, NewTagValue) = args | |||
log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`) | |||
log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \ | |||
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`, | |||
`NewTagValue`)) | |||
def soap_UpdateObject(self, *args, **kwargs): | |||
"""Modify, delete or insert object metadata.""" | |||
def soap_ImportResource(self, *args, **kwargs): | |||
"""Transfer a file from a remote source to a local | |||
destination in the Content Directory Service.""" | |||
(ObjectID, CurrentTagValue, NewTagValue) = args | |||
(SourceURI, DestinationURI) = args | |||
log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \ | |||
'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`, | |||
`NewTagValue`)) | |||
log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' % | |||
(`SourceURI`, `DestinationURI`)) | |||
def soap_ImportResource(self, *args, **kwargs): | |||
"""Transfer a file from a remote source to a local | |||
destination in the Content Directory Service.""" | |||
def soap_ExportResource(self, *args, **kwargs): | |||
"""Transfer a file from a local source to a remote | |||
destination.""" | |||
(SourceURI, DestinationURI) = args | |||
(SourceURI, DestinationURI) = args | |||
log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' % | |||
(`SourceURI`, `DestinationURI`)) | |||
log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' % | |||
(`SourceURI`, `DestinationURI`)) | |||
def soap_ExportResource(self, *args, **kwargs): | |||
"""Transfer a file from a local source to a remote | |||
destination.""" | |||
def soap_StopTransferResource(self, *args, **kwargs): | |||
"""Stop a file transfer initiated by ImportResource or | |||
ExportResource.""" | |||
(SourceURI, DestinationURI) = args | |||
(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): | |||
"""Query the progress of a file transfer initiated by | |||
an ImportResource or ExportResource action.""" | |||
(TransferID) = args | |||
(TransferID, TransferStatus, TransferLength, TransferTotal) = args | |||
log.msg('StopTransferResource(TransferID=%s)' % TransferID) | |||
log.msg('GetTransferProgress(TransferID=%s, TransferStatus=%s, ' \ | |||
'TransferLength=%s, TransferTotal=%s)' % | |||
(`TransferId`, `TransferStatus`, `TransferLength`, | |||
`TransferTotal`)) | |||
def soap_DeleteResource(self, *args, **kwargs): | |||
"""Delete a specified resource.""" | |||
def soap_GetTransferProgress(self, *args, **kwargs): | |||
"""Query the progress of a file transfer initiated by | |||
an ImportResource or ExportResource action.""" | |||
(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): | |||
"""Create a reference to an existing object.""" | |||
def soap_DeleteResource(self, *args, **kwargs): | |||
"""Delete a specified resource.""" | |||
(ContainerID, ObjectID) = args | |||
(ResourceURI) = args | |||
log.msg('CreateReference(ContainerID=%s, ObjectID=%s)' % | |||
(`ContainerID`, `ObjectID`)) | |||
log.msg('DeleteResource(ResourceURI=%s)' % `ResourceURI`) | |||
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): | |||
def __init__(self, title): | |||
resource.Resource.__init__(self) | |||
self.putChild('scpd.xml', static.File('content-directory-scpd.xml')) | |||
self.control = ContentDirectoryControl(title) | |||
self.putChild('control', self.control) | |||
def __init__(self, title): | |||
resource.Resource.__init__(self) | |||
self.putChild('scpd.xml', static.File('content-directory-scpd.xml')) | |||
self.control = ContentDirectoryControl(title) | |||
self.putChild('control', self.control) |
@@ -6,346 +6,346 @@ | |||
from elementtree.ElementTree import Element, SubElement, tostring, _ElementInterface | |||
class Resource: | |||
"""An object representing a resource.""" | |||
"""An object representing a resource.""" | |||
def __init__(self, data, protocolInfo): | |||
self.data = data | |||
self.protocolInfo = protocolInfo | |||
self.bitrate = None | |||
self.size = None | |||
def toElement(self): | |||
def __init__(self, data, protocolInfo): | |||
self.data = data | |||
self.protocolInfo = protocolInfo | |||
self.bitrate = None | |||
self.size = None | |||
root = Element('res') | |||
root.attrib['protocolInfo'] = self.protocolInfo | |||
root.text = self.data | |||
def toElement(self): | |||
if self.bitrate is not None: | |||
root.attrib['bitrate'] = str(self.bitrate) | |||
root = Element('res') | |||
root.attrib['protocolInfo'] = self.protocolInfo | |||
root.text = self.data | |||
if self.size is not None: | |||
root.attrib['size'] = str(self.size) | |||
if self.bitrate is not None: | |||
root.attrib['bitrate'] = str(self.bitrate) | |||
return root | |||
if self.size is not None: | |||
root.attrib['size'] = str(self.size) | |||
return root | |||
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' | |||
creator = None | |||
res = None | |||
writeStatus = None | |||
def __init__(self, cd, id, parentID, title, restricted = False, | |||
creator = None): | |||
def __init__(self, cd, id, parentID, title, restricted = False, | |||
creator = None): | |||
self.cd = cd | |||
self.id = id | |||
self.parentID = parentID | |||
self.title = title | |||
self.creator = creator | |||
self.cd = cd | |||
self.id = id | |||
self.parentID = parentID | |||
self.title = title | |||
self.creator = creator | |||
if restricted: | |||
self.restricted = '1' | |||
else: | |||
self.restricted = '0' | |||
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['parentID'] = self.parentID | |||
SubElement(root, 'dc:title').text = self.title | |||
SubElement(root, 'upnp:class').text = self.klass | |||
root.attrib['id'] = self.id | |||
root.attrib['parentID'] = self.parentID | |||
SubElement(root, 'dc:title').text = self.title | |||
SubElement(root, 'upnp:class').text = self.klass | |||
root.attrib['restricted'] = self.restricted | |||
root.attrib['restricted'] = self.restricted | |||
if self.creator is not None: | |||
SubElement(root, 'dc:creator').text = self.creator | |||
if self.creator is not None: | |||
SubElement(root, 'dc:creator').text = self.creator | |||
if self.res is not None: | |||
root.append(self.res.toElement()) | |||
if self.res is not None: | |||
root.append(self.res.toElement()) | |||
if self.writeStatus is not None: | |||
SubElement(root, 'upnp:writeStatus').text = self.writeStatus | |||
if self.writeStatus is not None: | |||
SubElement(root, 'upnp:writeStatus').text = self.writeStatus | |||
return root | |||
def toString(self): | |||
return tostring(self.toElement()) | |||
return root | |||
def toString(self): | |||
return tostring(self.toElement()) | |||
class Item(Object): | |||
"""A class used to represent atomic (non-container) content | |||
objects.""" | |||
"""A class used to represent atomic (non-container) content | |||
objects.""" | |||
klass = Object.klass + '.item' | |||
elementName = 'item' | |||
refID = None | |||
klass = Object.klass + '.item' | |||
elementName = 'item' | |||
refID = None | |||
def toElement(self): | |||
def toElement(self): | |||
root = Object.toElement(self) | |||
root = Object.toElement(self) | |||
if self.refID is not None: | |||
SubElement(root, 'refID').text = self.refID | |||
if self.refID is not None: | |||
SubElement(root, 'refID').text = self.refID | |||
return root | |||
return root | |||
class ImageItem(Item): | |||
klass = Item.klass + '.imageItem' | |||
klass = Item.klass + '.imageItem' | |||
class Photo(ImageItem): | |||
klass = ImageItem.klass + '.photo' | |||
klass = ImageItem.klass + '.photo' | |||
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 | |||
description = None | |||
longDescription = None | |||
publisher = None | |||
language = None | |||
relation = None | |||
rights = None | |||
def toElement(self): | |||
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: | |||
SubElement(root, 'dc:description').text = self.description | |||
if self.description is not None: | |||
SubElement(root, 'dc:description').text = self.description | |||
if self.longDescription is not None: | |||
SubElement(root, 'upnp:longDescription').text = \ | |||
self.longDescription | |||
if self.longDescription is not None: | |||
SubElement(root, 'upnp:longDescription').text = \ | |||
self.longDescription | |||
if self.publisher is not None: | |||
SubElement(root, 'dc:publisher').text = self.publisher | |||
if self.publisher is not None: | |||
SubElement(root, 'dc:publisher').text = self.publisher | |||
if self.language is not None: | |||
SubElement(root, 'dc:language').text = self.language | |||
if self.language is not None: | |||
SubElement(root, 'dc:language').text = self.language | |||
if self.relation is not None: | |||
SubElement(root, 'dc:relation').text = self.relation | |||
if self.relation is not None: | |||
SubElement(root, 'dc:relation').text = self.relation | |||
if self.rights is not None: | |||
SubElement(root, 'dc:rights').text = self.rights | |||
if self.rights is not None: | |||
SubElement(root, 'dc:rights').text = self.rights | |||
return root | |||
return root | |||
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 | |||
album = None | |||
originalTrackNumber = None | |||
playlist = None | |||
storageMedium = None | |||
contributor = None | |||
date = None | |||
artist = None | |||
album = None | |||
originalTrackNumber = None | |||
playlist = None | |||
storageMedium = None | |||
contributor = None | |||
date = None | |||
def toElement(self): | |||
def toElement(self): | |||
root = AudioItem.toElement(self) | |||
root = AudioItem.toElement(self) | |||
if self.artist is not None: | |||
SubElement(root, 'upnp:artist').text = self.artist | |||
if self.artist is not None: | |||
SubElement(root, 'upnp:artist').text = self.artist | |||
if self.album is not None: | |||
SubElement(root, 'upnp:album').text = self.album | |||
if self.album is not None: | |||
SubElement(root, 'upnp:album').text = self.album | |||
if self.originalTrackNumber is not None: | |||
SubElement(root, 'upnp:originalTrackNumber').text = \ | |||
self.originalTrackNumber | |||
if self.originalTrackNumber is not None: | |||
SubElement(root, 'upnp:originalTrackNumber').text = \ | |||
self.originalTrackNumber | |||
if self.playlist is not None: | |||
SubElement(root, 'upnp:playlist').text = self.playlist | |||
if self.playlist is not None: | |||
SubElement(root, 'upnp:playlist').text = self.playlist | |||
if self.storageMedium is not None: | |||
SubElement(root, 'upnp:storageMedium').text = self.storageMedium | |||
if self.storageMedium is not None: | |||
SubElement(root, 'upnp:storageMedium').text = self.storageMedium | |||
if self.contributor is not None: | |||
SubElement(root, 'dc:contributor').text = self.contributor | |||
if self.contributor is not None: | |||
SubElement(root, 'dc:contributor').text = self.contributor | |||
if self.date is not None: | |||
SubElement(root, 'dc:date').text = self.date | |||
if self.date is not None: | |||
SubElement(root, 'dc:date').text = self.date | |||
return root | |||
return root | |||
class AudioBroadcast(AudioItem): | |||
klass = AudioItem.klass + '.audioBroadcast' | |||
klass = AudioItem.klass + '.audioBroadcast' | |||
class AudioBook(AudioItem): | |||
klass = AudioItem.klass + '.audioBook' | |||
klass = AudioItem.klass + '.audioBook' | |||
class VideoItem(Item): | |||
klass = Item.klass + '.videoItem' | |||
klass = Item.klass + '.videoItem' | |||
class Movie(VideoItem): | |||
klass = VideoItem.klass + '.movie' | |||
klass = VideoItem.klass + '.movie' | |||
class VideoBroadcast(VideoItem): | |||
klass = VideoItem.klass + '.videoBroadcast' | |||
klass = VideoItem.klass + '.videoBroadcast' | |||
class MusicVideoClip(VideoItem): | |||
klass = VideoItem.klass + '.musicVideoClip' | |||
klass = VideoItem.klass + '.musicVideoClip' | |||
class PlaylistItem(Item): | |||
klass = Item.klass + '.playlistItem' | |||
klass = Item.klass + '.playlistItem' | |||
class TextItem(Item): | |||
klass = Item.klass + '.textItem' | |||
klass = Item.klass + '.textItem' | |||
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' | |||
childCount = property(lambda x: len(x)) | |||
createClass = None | |||
searchClass = None | |||
searchable = None | |||
elementName = 'container' | |||
childCount = property(lambda x: len(x)) | |||
createClass = None | |||
searchClass = None | |||
searchable = None | |||
def __init__(self, cd, id, parentID, title, restricted = 0, creator = None): | |||
Object.__init__(self, cd, id, parentID, title, restricted, creator) | |||
def __init__(self, cd, id, parentID, title, restricted = 0, creator = None): | |||
Object.__init__(self, cd, id, parentID, title, restricted, creator) | |||
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: | |||
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.createClass is not None: | |||
SubElement(root, 'upnp:createclass').text = self.createClass | |||
if self.searchable is not None: | |||
root.attrib['searchable'] = str(self.searchable) | |||
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 | |||
return root | |||
if self.searchable is not None: | |||
root.attrib['searchable'] = str(self.searchable) | |||
return root | |||
class Person(Container): | |||
klass = Container.klass + '.person' | |||
klass = Container.klass + '.person' | |||
class MusicArtist(Person): | |||
klass = Person.klass + '.musicArtist' | |||
klass = Person.klass + '.musicArtist' | |||
class PlaylistContainer(Container): | |||
klass = Container.klass + '.playlistContainer' | |||
klass = Container.klass + '.playlistContainer' | |||
class Album(Container): | |||
klass = Container.klass + '.album' | |||
klass = Container.klass + '.album' | |||
class MusicAlbum(Album): | |||
klass = Album.klass + '.musicAlbum' | |||
klass = Album.klass + '.musicAlbum' | |||
class PhotoAlbum(Album): | |||
klass = Album.klass + '.photoAlbum' | |||
klass = Album.klass + '.photoAlbum' | |||
class Genre(Container): | |||
klass = Container.klass + '.genre' | |||
klass = Container.klass + '.genre' | |||
class MusicGenre(Genre): | |||
klass = Genre.klass + '.musicGenre' | |||
klass = Genre.klass + '.musicGenre' | |||
class MovieGenre(Genre): | |||
klass = Genre.klass + '.movieGenre' | |||
klass = Genre.klass + '.movieGenre' | |||
class StorageSystem(Container): | |||
klass = Container.klass + '.storageSystem' | |||
klass = Container.klass + '.storageSystem' | |||
total = -1 | |||
used = -1 | |||
free = -1 | |||
maxpartition = -1 | |||
medium = 'UNKNOWN' | |||
total = -1 | |||
used = -1 | |||
free = -1 | |||
maxpartition = -1 | |||
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:storageMaxPartition').text = str(self.maxpartition) | |||
SubElement(root, 'upnp:storageMedium').text = self.medium | |||
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:storageMaxPartition').text = str(self.maxpartition) | |||
SubElement(root, 'upnp:storageMedium').text = self.medium | |||
return root | |||
return root | |||
class StorageVolume(Container): | |||
klass = Container.klass + '.storageVolume' | |||
klass = Container.klass + '.storageVolume' | |||
total = -1 | |||
used = -1 | |||
free = -1 | |||
medium = 'UNKNOWN' | |||
total = -1 | |||
used = -1 | |||
free = -1 | |||
medium = 'UNKNOWN' | |||
def toElement(self): | |||
def 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 | |||
root = Container.toElement(self) | |||
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): | |||
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): | |||
def __init__(self): | |||
_ElementInterface.__init__(self, '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:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp' | |||
def __init__(self): | |||
_ElementInterface.__init__(self, '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:upnp'] = 'urn:schemas-upnp-org:metadata-1-0/upnp' | |||
def addContainer(self, id, parentID, title, restricted = False): | |||
e = Container(id, parentID, title, restricted, creator = '') | |||
self.append(e.toElement()) | |||
def addContainer(self, id, parentID, title, restricted = False): | |||
e = Container(id, parentID, title, restricted, creator = '') | |||
self.append(e.toElement()) | |||
def addItem(self, item): | |||
self.append(item.toElement()) | |||
def addItem(self, item): | |||
self.append(item.toElement()) | |||
def numItems(self): | |||
return len(self) | |||
def numItems(self): | |||
return len(self) | |||
def toString(self): | |||
return tostring(self) | |||
def toString(self): | |||
return tostring(self) | |||
if __name__ == '__main__': | |||
root = DIDLElement() | |||
root.addContainer('0\Movie\\', '0\\', 'Movie') | |||
root.addContainer('0\Music\\', '0\\', 'Music') | |||
root.addContainer('0\Photo\\', '0\\', 'Photo') | |||
root.addContainer('0\OnlineMedia\\', '0\\', 'OnlineMedia') | |||
root = DIDLElement() | |||
root.addContainer('0\Movie\\', '0\\', 'Movie') | |||
root.addContainer('0\Music\\', '0\\', 'Music') | |||
root.addContainer('0\Photo\\', '0\\', 'Photo') | |||
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 SSDPServer(DatagramProtocol): | |||
"""A class implementing a SSDP server. The notifyReceived and | |||
searchReceived methods are called when the appropriate type of | |||
datagram is received by the server.""" | |||
"""A class implementing a SSDP server. The notifyReceived and | |||
searchReceived methods are called when the appropriate type of | |||
datagram is received by the server.""" | |||
# not used yet | |||
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ] | |||
elements = {} | |||
known = {} | |||
# not used yet | |||
stdheaders = [ ('Server', 'Twisted, UPnP/1.0, python-upnp'), ] | |||
elements = {} | |||
known = {} | |||
def doStop(self): | |||
def doStop(self): | |||
'''Make sure we send out the byebye notifications.''' | |||
self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT)) | |||
for st in self.known: | |||
self.doByebye(st) | |||
DatagramProtocol.doStop(self) | |||
def datagramReceived(self, data, (host, port)): | |||
"""Handle a received multicast datagram.""" | |||
# Break up message in to command and headers | |||
# TODO: use the email module after trimming off the request line.. | |||
# This gets us much better header support. | |||
header, payload = data.split('\r\n\r\n') | |||
lines = header.split('\r\n') | |||
cmd = string.split(lines[0], ' ') | |||
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:]) | |||
lines = filter(lambda x: len(x) > 0, lines) | |||
headers = [string.split(x, ':', 1) for x in lines] | |||
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) | |||
if cmd[0] == 'M-SEARCH' and cmd[1] == '*': | |||
# SSDP discovery | |||
self.discoveryRequest(headers, (host, port)) | |||
elif cmd[0] == 'NOTIFY' and cmd[1] == '*': | |||
# SSDP presence | |||
self.notifyReceived(headers, (host, port)) | |||
else: | |||
log.msg('Unknown SSDP command %s %s' % cmd) | |||
def discoveryRequest(self, headers, (host, port)): | |||
"""Process a discovery request. The response must be sent to | |||
the address specified by (host, port).""" | |||
log.msg('Discovery request for %s' % headers['st']) | |||
# Do we know about this service? | |||
if headers['st'] == 'ssdp:all': | |||
for i in self.known: | |||
hcopy = dict(headers.iteritems()) | |||
hcopy['st'] = i | |||
self.discoveryRequest(hcopy, (host, post)) | |||
return | |||
if not self.known.has_key(headers['st']): | |||
return | |||
# Generate a response | |||
response = [] | |||
response.append('HTTP/1.1 200 OK') | |||
for k, v in self.known[headers['st']].items(): | |||
response.append('%s: %s' % (k, v)) | |||
response.extend(('', '')) | |||
delay = random.randint(0, int(headers['mx'])) | |||
reactor.callLater(delay, self.transport.write, | |||
'\r\n'.join(response), (host, port)) | |||
def register(self, usn, st, location): | |||
"""Register a service or device that this SSDP server will | |||
respond to.""" | |||
log.msg('Registering %s' % st) | |||
self.known[st] = {} | |||
self.known[st]['USN'] = usn | |||
self.known[st]['LOCATION'] = location | |||
self.known[st]['ST'] = st | |||
self.known[st]['EXT'] = '' | |||
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp' | |||
self.known[st]['CACHE-CONTROL'] = 'max-age=1800' | |||
self.doNotify(st) | |||
def doByebye(self, st): | |||
"""Do byebye""" | |||
log.msg('Sending byebye notification for %s' % st) | |||
resp = [ 'NOTIFY * HTTP/1.1', | |||
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||
'NTS: ssdp:byebye', | |||
] | |||
stcpy = dict(self.known[st].iteritems()) | |||
stcpy['NT'] = stcpy['ST'] | |||
del stcpy['ST'] | |||
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) | |||
resp.extend(('', '')) | |||
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) | |||
def doNotify(self, st): | |||
"""Do notification""" | |||
log.msg('Sending alive notification for %s' % st) | |||
resp = [ 'NOTIFY * HTTP/1.1', | |||
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||
'NTS: ssdp:alive', | |||
] | |||
stcpy = dict(self.known[st].iteritems()) | |||
stcpy['NT'] = stcpy['ST'] | |||
del stcpy['ST'] | |||
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) | |||
resp.extend(('', '')) | |||
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) | |||
def notifyReceived(self, headers, (host, port)): | |||
"""Process a presence announcement. We just remember the | |||
details of the SSDP service announced.""" | |||
if headers['nts'] == 'ssdp:alive': | |||
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. | |||
self.transport.write('foobar', (SSDP_ADDR, SSDP_PORT)) | |||
for st in self.known: | |||
self.doByebye(st) | |||
DatagramProtocol.doStop(self) | |||
def datagramReceived(self, data, (host, port)): | |||
"""Handle a received multicast datagram.""" | |||
# Break up message in to command and headers | |||
# TODO: use the email module after trimming off the request line.. | |||
# This gets us much better header support. | |||
header, payload = data.split('\r\n\r\n') | |||
lines = header.split('\r\n') | |||
cmd = string.split(lines[0], ' ') | |||
lines = map(lambda x: x.replace(': ', ':', 1), lines[1:]) | |||
lines = filter(lambda x: len(x) > 0, lines) | |||
headers = [string.split(x, ':', 1) for x in lines] | |||
headers = dict(map(lambda x: (x[0].lower(), x[1]), headers)) | |||
if cmd[0] == 'M-SEARCH' and cmd[1] == '*': | |||
# SSDP discovery | |||
self.discoveryRequest(headers, (host, port)) | |||
elif cmd[0] == 'NOTIFY' and cmd[1] == '*': | |||
# SSDP presence | |||
self.notifyReceived(headers, (host, port)) | |||
else: | |||
log.msg('Unknown SSDP command %s %s' % cmd) | |||
def discoveryRequest(self, headers, (host, port)): | |||
"""Process a discovery request. The response must be sent to | |||
the address specified by (host, port).""" | |||
log.msg('Discovery request for %s' % headers['st']) | |||
# Do we know about this service? | |||
if headers['st'] == 'ssdp:all': | |||
for i in self.known: | |||
hcopy = dict(headers.iteritems()) | |||
hcopy['st'] = i | |||
self.discoveryRequest(hcopy, (host, post)) | |||
return | |||
if not self.known.has_key(headers['st']): | |||
return | |||
# Generate a response | |||
response = [] | |||
response.append('HTTP/1.1 200 OK') | |||
for k, v in self.known[headers['st']].items(): | |||
response.append('%s: %s' % (k, v)) | |||
response.extend(('', '')) | |||
delay = random.randint(0, int(headers['mx'])) | |||
reactor.callLater(delay, self.transport.write, | |||
'\r\n'.join(response), (host, port)) | |||
def register(self, usn, st, location): | |||
"""Register a service or device that this SSDP server will | |||
respond to.""" | |||
log.msg('Registering %s' % st) | |||
self.known[st] = {} | |||
self.known[st]['USN'] = usn | |||
self.known[st]['LOCATION'] = location | |||
self.known[st]['ST'] = st | |||
self.known[st]['EXT'] = '' | |||
self.known[st]['SERVER'] = 'Twisted, UPnP/1.0, python-upnp' | |||
self.known[st]['CACHE-CONTROL'] = 'max-age=1800' | |||
self.doNotify(st) | |||
def doByebye(self, st): | |||
"""Do byebye""" | |||
log.msg('Sending byebye notification for %s' % st) | |||
resp = [ 'NOTIFY * HTTP/1.1', | |||
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||
'NTS: ssdp:byebye', | |||
] | |||
stcpy = dict(self.known[st].iteritems()) | |||
stcpy['NT'] = stcpy['ST'] | |||
del stcpy['ST'] | |||
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) | |||
resp.extend(('', '')) | |||
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) | |||
def doNotify(self, st): | |||
"""Do notification""" | |||
log.msg('Sending alive notification for %s' % st) | |||
resp = [ 'NOTIFY * HTTP/1.1', | |||
'Host: %s:%d' % (SSDP_ADDR, SSDP_PORT), | |||
'NTS: ssdp:alive', | |||
] | |||
stcpy = dict(self.known[st].iteritems()) | |||
stcpy['NT'] = stcpy['ST'] | |||
del stcpy['ST'] | |||
resp.extend(map(lambda x: ': '.join(x), stcpy.iteritems())) | |||
resp.extend(('', '')) | |||
self.transport.write('\r\n'.join(resp), (SSDP_ADDR, SSDP_PORT)) | |||
def notifyReceived(self, headers, (host, port)): | |||
"""Process a presence announcement. We just remember the | |||
details of the SSDP service announced.""" | |||
if headers['nts'] == 'ssdp:alive': | |||
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 | |||
class Send(Protocol): | |||
def connectionMade(self): | |||
self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r | |||
def connectionMade(self): | |||
self.transport.write('''POST /ContentDirectory/control HTTP/1.1\r | |||
Host: 192.168.126.1:80\r | |||
User-Agent: POSIX, UPnP/1.0, Intel MicroStack/1.0.1423\r | |||
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"\r | |||
@@ -35,16 +35,16 @@ Content-Length: 511\r | |||
</s:Body>\r | |||
</s:Envelope>\r\n''') | |||
def dataReceived(self, data): | |||
print(data) | |||
def dataReceived(self, data): | |||
print(data) | |||
def connectionLost(self, reason): | |||
if reason.type != error.ConnectionDone: | |||
print str(reason) | |||
reactor.stop() | |||
def connectionLost(self, reason): | |||
if reason.type != error.ConnectionDone: | |||
print str(reason) | |||
reactor.stop() | |||
class SendFactory(ClientFactory): | |||
protocol = Send | |||
protocol = Send | |||
host = '192.168.126.128' | |||
port = 5643 | |||
@@ -15,38 +15,38 @@ from twisted.python import usage | |||
import sys, string, SOAPpy | |||
class UPnPSOAPProxy: | |||
"""A proxy for making UPnP SOAP calls.""" | |||
"""A proxy for making UPnP SOAP calls.""" | |||
def __init__(self, url): | |||
self.url = url | |||
def __init__(self, url): | |||
self.url = url | |||
def _cbGotResult(self, result): | |||
return SOAPpy.parseSOAPRPC(result) | |||
def _cbGotResult(self, 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 = string.replace(payload, '\n', '\r\n') | |||
action = \ | |||
'"urn:schemas-upnp-org:service:ContentDirectory:1#%s"' % method | |||
page = client.getPage(self.url, postdata = payload, method = 'POST', | |||
headers = {'Content-Type': 'text/xml', | |||
'SOAPACTION': action}) | |||
payload = SOAPpy.buildSOAP(args = args, kw = kwargs, method = method) | |||
payload = string.replace(payload, '\n', '\r\n') | |||
action = \ | |||
'"urn:schemas-upnp-org:service:ContentDirectory:1#%s"' % method | |||
page = client.getPage(self.url, postdata = payload, | |||
method = 'POST', headers = {'Content-Type': 'text/xml', | |||
'SOAPACTION': action}) | |||
return page.addCallback(self._cbGotResult) | |||
return page.addCallback(self._cbGotResult) | |||
class Options(usage.Options): | |||
pass | |||
pass | |||
def printResult(value): | |||
print value | |||
reactor.stop() | |||
print value | |||
reactor.stop() | |||
def printError(error): | |||
print 'error', error | |||
reactor.stop() | |||
print 'error', error | |||
reactor.stop() | |||
#proxy = UPnPSOAPProxy('http://192.168.126.128:5643/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('Browse', | |||
ObjectID = '0\\Music\\Genres\\Others\\chimes.wav', | |||
# BrowseFlag = 'BrowseDirectChildren', | |||
BrowseFlag = 'BrowseMetadata', | |||
Filter = '*', | |||
StartingIndex = 0, | |||
RequestedCount = 700, | |||
SortCriteria = None).addCallbacks(printResult, printError) | |||
ObjectID = '0\\Music\\Genres\\Others\\chimes.wav', | |||
#BrowseFlag = 'BrowseDirectChildren', | |||
BrowseFlag = 'BrowseMetadata', | |||
Filter = '*', | |||
StartingIndex = 0, | |||
RequestedCount = 700, | |||
SortCriteria = None).addCallbacks(printResult, printError) | |||
reactor.run() |
@@ -8,11 +8,9 @@ from twisted.web import soap | |||
import SOAPpy | |||
class UPnPPublisher(soap.SOAPPublisher): | |||
"""UPnP requires OUT parameters to be returned in a slightly | |||
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) | |||
"""UPnP requires OUT parameters to be returned in a slightly | |||
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) |
@@ -12,7 +12,7 @@ from xml.dom import minidom | |||
str = "" | |||
for s in sys.stdin.readlines(): | |||
str = str + s[:-1] # Eat trailing \n | |||
str = str + s[:-1] # Eat trailing \n | |||
doc = minidom.parseString(str) | |||
print doc.toprettyxml(indent = " ") |