|
- # Licensed under the MIT license
- # http://opensource.org/licenses/mit-license.php
- # Copyright 2005, Tim Potter <tpot@samba.org>
- # Copyright 2006 John-Mark Gurney <gurney_j@resnet.uoregon.edu>
- #
- # $Id$
- #
-
- #
- # This module implements the Content Directory Service (CDS) service
- # type as documented in the ContentDirectory:1 Service Template
- # Version 1.01
- #
-
- #
- # TODO: Figure out a nicer pattern for debugging soap server calls as
- # twisted swallows the tracebacks. At the moment I'm going:
- #
- # try:
- # ....
- # except:
- # traceback.print_exc(file = log.logfile)
- #
-
- from twisted.python import log
- from twisted.web import resource, static
-
- from elementtree.ElementTree import Element, SubElement, tostring
- from upnp import UPnPPublisher, errorCode
- from DIDLLite import DIDLElement, Container, Movie, Resource, MusicTrack
-
- from twisted.internet import defer
- from twisted.python import failure
-
- import traceback
- from urllib import quote
-
- class doRecall(defer.Deferred):
- '''A class that will upon any callback from the Deferred object passed
- in, recall fun(*args, **kwargs), just as if a maybeDeferred has been
- processed.
-
- The idea is to let something deeper called by something sync "abort"
- the call until it's ready, and then reattempt. This isn't the best
- method as we throw away work, but it can be easier to implement.
-
- Example:
- def wrapper(five):
- try:
- return doacall(five)
- except defer.Deferred, x:
- return doRecallgen(x, wrapper, five)
-
- If doacall works, everything is fine, but if a Deferred object is
- raised, we put it in a doRecall class and return the deferred object
- generated by doRecall.'''
-
- def __init__(self, argdef, fun, *args, **kwargs):
- self.fun = fun
- self.args = args
- self.kwargs = kwargs
- self.defer = defer.Deferred()
-
- argdef.addCallback(self._done)
-
- def _done(self, *args, **kwargs):
- ret = self.fun(*self.args, **self.kwargs)
- if isinstance(ret, failure.Failure):
- self.defer.errback(ret)
- elif isinstance(ret, defer.Deferred):
- # We are fruther delayed, continue.
- ret.addCallback(self._done)
- else:
- self.defer.callback(ret)
-
- def doRecallgen(defer, fun, *args, **kwargs):
- i = doRecall(defer, fun, *args, **kwargs)
- return i.defer
-
- class ContentDirectoryControl(UPnPPublisher, dict):
- """This class implements the CDS actions over SOAP."""
-
- urlbase = property(lambda x: x._urlbase)
-
- def getnextID(self):
- ret = str(self.nextID)
- self.nextID += 1
- return ret
-
- def addContainer(self, parent, title, klass = Container, *args, **kwargs):
- ret = self.addObject(parent, klass, title, *args, **kwargs)
- self.children[ret] = self[ret]
- return ret
-
- def addItem(self, parent, klass, title, *args, **kwargs):
- if issubclass(klass, Container):
- return self.addContainer(parent, title, klass, *args, **kwargs)
- else:
- return self.addObject(parent, klass, title, *args, **kwargs)
-
- def addObject(self, parent, klass, title, *args, **kwargs):
- '''If the generated object (by klass) has an attribute content, it is installed into the web server.'''
- assert isinstance(self[parent], Container)
- nid = self.getnextID()
- i = klass(self, nid, parent, title, *args, **kwargs)
- if hasattr(i, 'content'):
- self.webbase.putChild(nid, i.content)
- self.children[parent].append(i)
- self[i.id] = i
- return i.id
-
- def delItem(self, id):
- if not self.has_key(id):
- log.msg('already removed:', id)
- return
- log.msg('removing:', id)
- if isinstance(self[id], Container):
- while self.children[id]:
- self.delItem(self.children[id][0])
- assert len(self.children[id]) == 0
- del self.children[id]
- # Remove from parent
- self.children[self[id].parentID].remove(self[id])
- # Remove content
- if hasattr(self[id], 'content'):
- self.webbase.delEntity(id)
- del self[id]
-
- def getchildren(self, item):
- assert isinstance(self[item], Container)
- return self.children[item][:]
-
- def __init__(self, title, *args, **kwargs):
- super(ContentDirectoryControl, self).__init__(*args)
- self.webbase = kwargs['webbase']
- self._urlbase = kwargs['urlbase']
- del kwargs['webbase'], kwargs['urlbase']
- fakeparent = '-1'
- self.nextID = 0
- self.children = { fakeparent: []}
- self[fakeparent] = Container(None, None, '-1', 'fake')
- root = self.addContainer(fakeparent, title, **kwargs)
- assert root == '0'
- del self[fakeparent]
- del self.children[fakeparent]
-
- # Required actions
-
- def soap_GetSearchCapabilities(self, *args, **kwargs):
- """Required: Return the searching capabilities supported by the device."""
-
- 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."""
-
- log.msg('GetSortCapabilities()')
- return { 'SortCapabilitiesResponse': { 'SortCaps': '' }}
-
- def soap_GetSystemUpdateID(self, *args, **kwargs):
- """Required: Return the current value of state variable SystemUpdateID."""
-
- log.msg('GetSystemUpdateID()')
- return { 'SystemUpdateIdResponse': { 'Id': self['0'].updateID }}
-
- BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren')
-
- def soap_Browse(self, *args):
- try:
- return self.thereal_soap_Browse(*args)
- except defer.Deferred, x:
- return doRecallgen(x, self.soap_Browse, *args)
-
- def thereal_soap_Browse(self, *args):
- """Required: Incrementally browse the native heirachy of the Content
- Directory objects exposed by the Content Directory Service."""
-
- (ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount,
- SortCriteria) = args
- StartingIndex = int(StartingIndex)
- RequestedCount = int(RequestedCount)
-
- log.msg('Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, '
- 'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' %
- (`ObjectID`, `BrowseFlag`, `Filter`, `StartingIndex`,
- `RequestedCount`, `SortCriteria`))
-
- didl = DIDLElement()
- result = {}
-
- # check to see if object needs to be updated
- self[ObjectID].checkUpdate()
-
- # return error code if we don't exist
- if ObjectID not in self:
- raise errorCode(701)
-
- if BrowseFlag == 'BrowseDirectChildren':
- ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
- # filter out the ones that don't exist anymore, we need
- # to check against None, since some dirs might be empty
- # (of valid content) but exist.
- # XXX - technically if list changed, we need to get
- # some new ones by looping till we have a complete
- # list.
- ochup = filter(lambda x, s = self:
- s[x.id].checkUpdate() is not None, ch)
- # XXX - are we suppose to be calling addContainer
- # for Containers instead of always addItem?
- #log.msg('ochup:', `ochup`)
- filter(lambda x, d = didl: d.addItem(x) and None, ochup)
- total = len(self.getchildren(ObjectID))
- else:
- didl.addItem(self[ObjectID])
- total = 1
-
- result = {'BrowseResponse': {'Result': didl.toString() ,
- 'NumberReturned': didl.numItems(),
- 'TotalMatches': total,
- 'UpdateID': self[ObjectID].updateID }}
-
- #log.msg('Returning: %s' % result)
-
- return result
-
- # Optional actions
-
- def soap_Search(self, *args, **kwargs):
- """Search for objects that match some search criteria."""
-
- (ContainerID, SearchCriteria, Filter, StartingIndex,
- RequestedCount, SortCriteria) = args
-
- 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, Elements) = args
-
- log.msg('CreateObject(ContainerID=%s, Elements=%s)' %
- (`ContainerID`, `Elements`))
-
- def soap_DestroyObject(self, *args, **kwargs):
- """Destroy the specified object."""
-
- (ObjectID) = args
-
- log.msg('DestroyObject(ObjectID=%s)' % `ObjectID`)
-
- def soap_UpdateObject(self, *args, **kwargs):
- """Modify, delete or insert object metadata."""
-
- (ObjectID, CurrentTagValue, NewTagValue) = args
-
- log.msg('UpdateObject(ObjectID=%s, CurrentTagValue=%s, ' \
- 'NewTagValue=%s)' % (`ObjectID`, `CurrentTagValue`,
- `NewTagValue`))
-
- def soap_ImportResource(self, *args, **kwargs):
- """Transfer a file from a remote source to a local
- destination in the Content Directory Service."""
-
- (SourceURI, DestinationURI) = args
-
- log.msg('ImportResource(SourceURI=%s, DestinationURI=%s)' %
- (`SourceURI`, `DestinationURI`))
-
- def soap_ExportResource(self, *args, **kwargs):
- """Transfer a file from a local source to a remote
- destination."""
-
- (SourceURI, DestinationURI) = args
-
- log.msg('ExportResource(SourceURI=%s, DestinationURI=%s)' %
- (`SourceURI`, `DestinationURI`))
-
- def soap_StopTransferResource(self, *args, **kwargs):
- """Stop a file transfer initiated by ImportResource or
- ExportResource."""
-
- (TransferID) = args
-
- log.msg('StopTransferResource(TransferID=%s)' % TransferID)
-
- def soap_GetTransferProgress(self, *args, **kwargs):
- """Query the progress of a file transfer initiated by
- an ImportResource or ExportResource action."""
-
- (TransferID, TransferStatus, TransferLength, TransferTotal) = args
-
- 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."""
-
- (ResourceURI) = args
-
- 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`))
-
- def __repr__(self):
- return '<ContentDirectoryControl: cnt: %d, urlbase: %s, nextID: %d>' % (len(self), `self.urlbase`, self.nextID)
-
- class ContentDirectoryServer(resource.Resource):
- def __init__(self, title, *args, **kwargs):
- resource.Resource.__init__(self)
- self.putChild('scpd.xml', static.File('content-directory-scpd.xml'))
- self.control = ContentDirectoryControl(title, *args, **kwargs)
- self.putChild('control', self.control)
|