text: #!/usr/bin/python
  #
  # Licensed to the Apache Software Foundation (ASF) under one
  # or more contributor license agreements.  See the NOTICE file
  # distributed with this work for additional information
  # regarding copyright ownership.  The ASF licenses this file
  # to you under the Apache License, Version 2.0 (the
  # "License"); you may not use this file except in compliance
  # with the License.  You may obtain a copy of the License at
  #
  #   http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing,
  # software distributed under the License is distributed on an
  # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  # KIND, either express or implied.  See the License for the
  # specific language governing permissions and limitations
  # under the License.
  
  """Defines the generic robot classes.
  
  This module provides the Robot class and RobotListener interface,
  as well as some helper functions for web requests and responses.
  """
  
  import logging
  import sys
  
  try:
    __import__('google3') # setup internal test environment
  except ImportError:
    pass
  
  import simplejson
  import blip
  import errors
  import ops
  import simplejson
  import wavelet
  import waveservice
  
  DEFAULT_PROFILE_URL = (
      'http://code.google.com/apis/wave/extensions/robots/python-tutorial.html')
  
  
  class Robot(object):
    """Robot metadata class.
  
    This class holds on to basic robot information like the name and profile.
    It also maintains the list of event handlers and cron jobs and
    dispatches events to the appropriate handlers.
    """
  
    def __init__(self, name, image_url='', profile_url=DEFAULT_PROFILE_URL):
      """Initializes self with robot information.
  
      Args:
        name: The name of the robot
        image_url: (optional) url of an image that should be used as the avatar
            for this robot.
        profile_url: (optional) url of a webpage with more information about
            this robot.
      """
      self._handlers = {}
      self._name = name
      self._verification_token = None
      self._st = None
      self._waveservice = waveservice.WaveService()
      self._profile_handler = None
      self._image_url = image_url
      self._profile_url = profile_url
      self._capability_hash = 0
      self._consumer_key = None
      self._http_post = None
  
    @property
    def name(self):
      """Returns the name of the robot."""
      return self._name
  
    @property
    def image_url(self):
      """Returns the URL of the avatar image."""
      return self._image_url
  
    @property
    def profile_url(self):
      """Returns the URL of an info page for the robot."""
      return self._profile_url
  
    def get_verification_token_info(self):
      """Returns the verification token and ST parameter."""
      return self._verification_token, self._st
  
    def get_waveservice(self):
      """Return the currently installed waveservice if available.
  
      Raises:
        Error: if no service is installed.
      """
      if self._waveservice is None:
        raise errors.Error('Oauth has not been setup')
      return self._waveservice
  
    def capabilities_hash(self):
      """Return the capabilities hash as a hex string."""
      return hex(self._capability_hash)
  
    def register_handler(self, event_class, handler, context=None, filter=None):
      """Registers a handler on a specific event type.
  
      Multiple handlers may be registered on a single event type and are
      guaranteed to be called in order of registration.
  
      The handler takes two arguments, the event object and the corresponding
      wavelet.
  
      Args:
        event_class: An event to listen for from the classes defined in the
            events module.
  
        handler: A function handler which takes two arguments, the wavelet for
            the event and the event object.
  
        context: The context to provide for this handler.
  
        filter: Depending on the event, a filter can be specified that restricts
            for which values the event handler will be called from the server.
            Valuable to restrict the amount of traffic send to the robot.
      """
      payload = (handler, event_class, context, filter)
      self._handlers.setdefault(event_class.type, []).append(payload)
      if isinstance(context, list):
        context = ','.join(context)
      self._capability_hash = (self._capability_hash * 13 +
                               hash(ops.PROTOCOL_VERSION) +
                               hash(event_class.type) +
                               hash(context) +
                               hash(filter)) & 0xfffffff
  
    def set_verification_token_info(self, token, st=None):
      """Set the verification token used in the ownership verification.
  
      /wave/robot/register starts this process up and will produce this token.
  
      Args:
        token: the token provided by /wave/robot/register.
  
        st: optional parameter to verify the request for the token came from
            the wave server.
      """
      self._verification_token = token
      self._st = st
  
    def set_http_post(self, http_post):
      """Set the http_post handler to use when posting."""
      self._http_post = http_post
      if self._waveservice:
        self._waveservice.set_http_post(http_post)
  
    def setup_oauth(self, consumer_key, consumer_secret,
        server_rpc_base='https://www-opensocial.googleusercontent.com/api/rpc'):
      """Configure this robot to use the oauth'd json rpc.
  
      Args:
        consumer_key: consumer key received from the verification process.
  
        consumer_secret: secret received from the verification process.
  
        server_rpc_base: url of the rpc gateway to use. Specify None for default.
            For wave preview, https://www-opensocial.googleusercontent.com/api/rpc
            should be used.
            For wave sandbox,
            https://www-opensocial-sandbox.googleusercontent.com/api/rpc should be used.
      """
  
      consumer_key_prefix = ''
      # NOTE(ljvderijk): Present for backwards capability.
      if server_rpc_base in [waveservice.WaveService.SANDBOX_RPC_URL,
                             waveservice.WaveService.RPC_URL]:
        consumer_key_prefix = 'google.com:'
  
      self._consumer_key = consumer_key_prefix + consumer_key
      self._waveservice = waveservice.WaveService(
          consumer_key=consumer_key,
          consumer_secret=consumer_secret,
          server_rpc_base=server_rpc_base,
          http_post=self._http_post)
  
    def register_profile_handler(self, handler):
      """Sets the profile handler for this robot.
  
      The profile handler will be called when a profile is needed. The handler
      gets passed the name for which a profile is needed or None for the
      robot itself. A dictionary with keys for name, imageUrl and
      profileUrl should be returned.
      """
      self._profile_handler = handler
  
    def capabilities_xml(self):
      """Return this robot's capabilities as an XML string."""
      lines = []
      for capability, payloads in self._handlers.items():
        for payload in payloads:
          handler, event_class, context, filter = payload
          line = '  <w:capability name="%s"' % capability
          if context:
            if isinstance(context, list):
              context = ','.join(context)
            line += ' context="%s"' % context
          if filter:
            line += ' filter="%s"' % filter
          line += '/>\n'
          lines.append(line)
      if self._consumer_key:
        oauth_tag = '<w:consumer_key>%s</w:consumer_key>\n' % self._consumer_key
      else:
        oauth_tag = ''
      return ('<?xml version="1.0"?>\n'
              '<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">\n'
              '<w:version>%s</w:version>\n'
              '%s'
              '<w:protocolversion>%s</w:protocolversion>\n'
              '<w:capabilities>\n'
              '%s'
              '</w:capabilities>\n'
              '</w:robot>\n') % (self.capabilities_hash(),
                                 oauth_tag,
                                 ops.PROTOCOL_VERSION,
                                 '\n'.join(lines))
  
    def profile_json(self, name=None):
      """Returns a JSON representation of the profile.
  
      This method is called both for the basic profile of the robot and to
      get a proxying for profile, in which case name is set. By default
      the information supplied at registration is returned.
  
      Use register_profile_handler to override this default behavior.
      """
      if self._profile_handler:
        data = self._profile_handler(name)
      else:
        data = {'name': self.name,
                'imageUrl': self.image_url,
                'profileUrl': self.profile_url}
      return simplejson.dumps(data)
  
    def process_events(self, json):
      """Process an incoming set of events encoded as json."""
      parsed = simplejson.loads(json)
      pending_ops = ops.OperationQueue()
      event_wavelet = self.get_waveservice()._wavelet_from_json(parsed, pending_ops)
  
      for event_data in parsed['events']:
        for payload in self._handlers.get(event_data['type'], []):
          handler, event_class, context, filter = payload
          event = event_class(event_data, event_wavelet)
          handler(event, event_wavelet)
  
      pending_ops.set_capability_hash(self.capabilities_hash())
      return simplejson.dumps(pending_ops.serialize())
  
    def new_wave(self, domain, participants=None, message='', proxy_for_id=None,
                 submit=False):
      """Create a new wave with the initial participants on it.
  
      A new wave is returned with its own operation queue. It the
      responsibility of the caller to make sure this wave gets
      submitted to the server, either by calling robot.submit() or
      by calling .submit_with() on the returned wave.
  
      Args:
        domain: the domain to create the wavelet on. This should
            in general correspond to the domain of the incoming
            wavelet. (wavelet.domain). Exceptions are situations
            where the robot is calling new_wave outside of an
            event or when the server is handling multiple domains.
  
        participants: initial participants on the wave. The robot
            as the creator of the wave is always added.
  
        message: a string that will be passed back to the robot
            when the WAVELET_CREATOR event is fired. This is a
            lightweight way to pass around state.
  
        submit: if true, use the active gateway to make a round
            trip to the server. This will return immediately an
            actual waveid/waveletid and blipId for the root blip.
  
      """
      return self.get_waveservice().new_wave(
          domain, participants, message, proxy_for_id, submit)
  
    def fetch_wavelet(self, wave_id, wavelet_id=None, proxy_for_id=None,
                      raw_deltas_from_version=-1, return_raw_snapshot=False):
      """Use the REST interface to fetch a wave and return it.
  
      The returned wavelet contains a snapshot of the state of the
      wavelet at that point. It can be used to modify the wavelet,
      but the wavelet might change in between, so treat carefully.
  
      Also note that the wavelet returned has its own operation
      queue. It the responsibility of the caller to make sure this
      wavelet gets submited to the server, either by calling
      robot.submit() or by calling .submit_with() on the returned
      wavelet.
      """
      return self.get_waveservice().fetch_wavelet(
          wave_id, wavelet_id, proxy_for_id, raw_deltas_from_version,
          return_raw_snapshot)
  
    def blind_wavelet(self, json, proxy_for_id=None):
      """Construct a blind wave from a json string.
  
      Call this method if you have a snapshot of a wave that you
      want to operate on outside of an event. Since the wave might
      have changed since you last saw it, you should take care to
      submit operations that are as safe as possible.
  
      Args:
        json: a json object or string containing at least a key
          wavelet defining the wavelet and a key blips defining the
          blips in the view.
  
        proxy_for_id: the proxying information that will be set on the wavelet's
          operation queue.
  
      Returns:
        A new wavelet with its own operation queue. It the
        responsibility of the caller to make sure this wavelet gets
        submited to the server, either by calling robot.submit() or
        by calling .submit_with() on the returned wavelet.
      """
      return self.get_waveservice().blind_wavelet(json, proxy_for_id)
  
    def submit(self, wavelet_to_submit):
      """Submit the pending operations associated with wavelet_to_submit.
  
      Typically the wavelet will be the result of fetch_wavelet, blind_wavelet
      or new_wave.
      """
      return self.get_waveservice().submit(wavelet_to_submit)
  