|
- # Licensed under the MIT license
- # http://opensource.org/licenses/mit-license.php
- # Copyright 2005, Tim Potter <tpot@samba.org>
- # Copyright 2006-2008 John-Mark Gurney <jmg@funkthat.com>
-
- __version__ = '$Change$'
- # $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)
- #
-
- reqname = 'requests'
-
- 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 debug
- 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."""
-
- namespace = 'urn:schemas-upnp-org:service:ContentDirectory:1'
- updateID = property(lambda x: x['0'].updateID)
- 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)
- #log.msg('children:', `self.children[parent]`, `i`)
- self.children[parent].append(i)
- self[i.id] = i
- return i.id
-
- def has_key(self, key):
- return dict.has_key(self, key)
-
- 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):
- #log.msg('children:', Container.__repr__(self.children[id]), map(None, self.children[id]))
- while self.children[id]:
- self.delItem(self.children[id][0].id)
- 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):
- debug.insertringbuf(reqname)
- 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 { '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 { 'SortCaps': '' }
-
- def soap_GetSystemUpdateID(self, *args, **kwargs):
- """Required: Return the current value of state variable SystemUpdateID."""
-
- return { 'Id': self.updateID }
-
- BrowseFlags = ('BrowseMetaData', 'BrowseDirectChildren')
-
- def soap_Browse(self, *args):
- l = {}
- debug.appendnamespace(reqname, l)
- if self.has_key(args[0]):
- l['object'] = self[args[0]]
- l['query'] = 'Browse(ObjectID=%s, BrowseFlags=%s, Filter=%s, ' \
- 'StartingIndex=%s RequestedCount=%s SortCriteria=%s)' % \
- tuple(map(repr, args))
-
- try:
- ret = self.thereal_soap_Browse(*args)
- except defer.Deferred, x:
- ret = doRecallgen(x, self.soap_Browse, *args)
-
- l['response'] = ret
-
- return ret
-
- 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)
-
- didl = DIDLElement()
-
- # return error code if we don't exist anymore
- if ObjectID not in self:
- raise errorCode(701)
-
- # check to see if object needs to be updated
- self[ObjectID].checkUpdate()
-
- # make sure we still exist, we could of deleted ourself
- if ObjectID not in self:
- raise errorCode(701)
-
- if BrowseFlag == 'BrowseDirectChildren':
- ch = self.getchildren(ObjectID)[StartingIndex: StartingIndex + RequestedCount]
- for i in ch:
- if i.needupdate:
- i.checkUpdate()
- didl.addItem(i)
- total = len(self.getchildren(ObjectID))
- else:
- didl.addItem(self[ObjectID])
- total = 1
-
- r = { 'Result': didl.toString(), 'TotalMatches': total,
- 'NumberReturned': didl.numItems(), }
-
- if hasattr(self[ObjectID], 'updateID'):
- r['UpdateID'] = self[ObjectID].updateID
- else:
- r['UpdateID'] = self.updateID
-
- return r
-
- # 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)
|