From f4e2791c897651d52980d35b84115e7c7f85a249 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 21 Jun 2006 18:23:18 +0000 Subject: Big refactor of the directory structure and packages to reflect private/public --- (limited to 'shell') diff --git a/shell/Makefile.am b/shell/Makefile.am new file mode 100644 index 0000000..1080b7d --- /dev/null +++ b/shell/Makefile.am @@ -0,0 +1,8 @@ +sugardir = $(pythondir)/sugar/shell +sugar_PYTHON = \ + __init__.py \ + activity.py \ + shell.py \ + Owner.py \ + PresenceWindow.py \ + StartPage.py diff --git a/shell/Owner.py b/shell/Owner.py new file mode 100644 index 0000000..bd8771d --- /dev/null +++ b/shell/Owner.py @@ -0,0 +1,57 @@ +import os +import random +import base64 + +from sugar import env +from sugar.presence import Service +from sugar.presence import Buddy +from sugar.presence import PresenceService +from sugar.p2p import Stream + +class ShellOwner(object): + """Class representing the owner of this machine/instance. This class + runs in the shell and serves up the buddy icon and other stuff. It's the + server portion of the Owner, paired with the client portion in Buddy.py.""" + def __init__(self): + nick = env.get_nick_name() + user_dir = env.get_user_dir() + if not os.path.exists(user_dir): + try: + os.makedirs(user_dir) + except OSError: + print "Could not create user directory." + + self._icon = None + for fname in os.listdir(user_dir): + if not fname.startswith("buddy-icon."): + continue + fd = open(os.path.join(user_dir, fname), "r") + self._icon = fd.read() + fd.close() + break + + # Our presence service + port = random.randint(40000, 65000) + properties = {} + self._service = Service.Service(nick, Buddy.PRESENCE_SERVICE_TYPE, + domain="", address=None, port=port, properties=properties) + print "Owner '%s' using port %d" % (nick, port) + + self._icon_stream = Stream.Stream.new_from_service(self._service) + self._icon_stream.register_reader_handler(self._handle_buddy_icon_request, "get_buddy_icon") + + # Announce ourselves to the world + self._pservice = PresenceService.PresenceService.get_instance() + self._pservice.start() + self._pservice.register_service(self._service) + + def _handle_buddy_icon_request(self): + """XMLRPC method, return the owner's icon encoded with base64.""" + if self._icon: + return base64.b64encode(self._icon) + return "" + + def set_icon(self, icon): + """Can only set icon in constructor for now.""" + pass + diff --git a/shell/PresenceWindow.py b/shell/PresenceWindow.py new file mode 100644 index 0000000..c9ec558 --- /dev/null +++ b/shell/PresenceWindow.py @@ -0,0 +1,154 @@ +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +from sugar.presence.PresenceService import PresenceService + +class PresenceWindow(gtk.Window): + _MODEL_COL_NICK = 0 + _MODEL_COL_ICON = 1 + _MODEL_COL_BUDDY = 2 + _MODEL_COL_VISIBLE = 3 + + def __init__(self, activity_container): + gtk.Window.__init__(self) + + self._activity_container = activity_container + self._activity = None + + self._pservice = PresenceService.get_instance() + self._pservice.connect("buddy-appeared", self._on_buddy_appeared_cb) + self._pservice.connect("buddy-disappeared", self._on_buddy_disappeared_cb) + self._pservice.set_debug(True) + self._pservice.start() + + self._setup_ui() + + def _is_buddy_visible(self, buddy): + if self._activity: + activity_type = self._activity.get_default_type() + service = buddy.get_service_of_type(activity_type, self._activity) + return service is not None + else: + return True + + def _update_buddies_visibility(self): + for row in self._buddy_store: + row[self._MODEL_COL_VISIBLE] = self._is_buddy_visible(row[self._MODEL_COL_BUDDY]) + + def set_activity(self, activity): + self._activity = activity + self._update_buddies_visibility() + + def _setup_ui(self): + vbox = gtk.VBox(False, 6) + vbox.set_border_width(12) + + label = gtk.Label("Who's around:") + label.set_alignment(0.0, 0.5) + vbox.pack_start(label, False) + label.show() + + self._buddy_store = gtk.ListStore(gobject.TYPE_STRING, + gtk.gdk.Pixbuf, + gobject.TYPE_PYOBJECT, + bool) + buddy_list_model = self._buddy_store.filter_new() + buddy_list_model.set_visible_column(self._MODEL_COL_VISIBLE) + + sw = gtk.ScrolledWindow() + sw.set_shadow_type(gtk.SHADOW_IN) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + + self._buddy_list_view = gtk.TreeView(buddy_list_model) + self._buddy_list_view.set_headers_visible(False) + self._buddy_list_view.connect("cursor-changed", self._on_buddyList_buddy_selected) + self._buddy_list_view.connect("row-activated", self._on_buddyList_buddy_double_clicked) + + sw.add(self._buddy_list_view) + self._buddy_list_view.show() + + renderer = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn("", renderer, pixbuf=self._MODEL_COL_ICON) + column.set_resizable(False) + column.set_expand(False); + self._buddy_list_view.append_column(column) + + renderer = gtk.CellRendererText() + column = gtk.TreeViewColumn("", renderer, text=self._MODEL_COL_NICK) + column.set_resizable(True) + column.set_sizing("GTK_TREE_VIEW_COLUMN_GROW_ONLY"); + column.set_expand(True); + self._buddy_list_view.append_column(column) + + vbox.pack_start(sw) + sw.show() + + button_box = gtk.HButtonBox() + + share_button = gtk.Button('Share') + share_button.connect('clicked', self._share_button_clicked_cb) + button_box.pack_start(share_button) + share_button.show() + + vbox.pack_start(button_box, False) + button_box.show() + + self.add(vbox) + vbox.show() + + def _share_button_clicked_cb(self, button): + self._activity_container.current_activity.publish() + + def _on_buddyList_buddy_selected(self, view, *args): + (model, aniter) = view.get_selection().get_selected() + name = model.get(aniter, self._MODEL_COL_NICK) + + def _on_buddyList_buddy_double_clicked(self, view, *args): + """ Select the chat for this buddy or group """ + (model, aniter) = view.get_selection().get_selected() + chat = None + buddy = view.get_model().get_value(aniter, self._MODEL_COL_BUDDY) + if buddy and not self._chats.has_key(buddy): + #chat = BuddyChat(self, buddy) + #self._chats[buddy] = chat + #chat.connect_to_shell() + pass + + def __buddy_icon_changed_cb(self, buddy): + it = self._get_iter_for_buddy(buddy) + self._buddy_store.set(it, self._MODEL_COL_ICON, buddy.get_icon_pixbuf()) + + def _on_buddy_appeared_cb(self, pservice, buddy): + if buddy.is_owner(): + # Do not show ourself in the buddy list + return + + aniter = self._buddy_store.append(None) + self._buddy_store.set(aniter, + self._MODEL_COL_NICK, buddy.get_nick_name(), + self._MODEL_COL_BUDDY, buddy, + self._MODEL_COL_VISIBLE, self._is_buddy_visible(buddy)) + buddy.connect('icon-changed', self.__buddy_icon_changed_cb) + buddy.connect('service-added', self.__buddy_service_added_cb) + buddy.connect('service-removed', self.__buddy_service_removed_cb) + + def __buddy_service_added_cb(self, buddy, service): + self._update_buddies_visibility() + + def __buddy_service_removed_cb(self, buddy, service): + self._update_buddies_visibility() + + def _on_buddy_disappeared_cb(self, pservice, buddy): + aniter = self._get_iter_for_buddy(buddy) + if aniter: + self._buddy_store.remove(aniter) + + def _get_iter_for_buddy(self, buddy): + aniter = self._buddy_store.get_iter_first() + while aniter: + list_buddy = self._buddy_store.get_value(aniter, self._MODEL_COL_BUDDY) + if buddy == list_buddy: + return aniter + aniter = self._buddy_store.iter_next(aniter) diff --git a/shell/StartPage.py b/shell/StartPage.py new file mode 100644 index 0000000..4ceeec3 --- /dev/null +++ b/shell/StartPage.py @@ -0,0 +1,295 @@ +import pygtk +pygtk.require('2.0') +import gtk +import pango +import dbus +import cgi +import xml.sax.saxutils +import gobject + +from google import google +from sugar.presence.PresenceService import PresenceService + +_BROWSER_ACTIVITY_TYPE = "_web_olpc._udp" + +_COLUMN_TITLE = 0 +_COLUMN_ADDRESS = 1 +_COLUMN_SUBTITLE = 2 +_COLUMN_SERVICE = 3 + +class SearchHelper(object): + def __init__(self, activity_uid): + self.search_uid = activity_uid + self.found = False + +class SearchModel(gtk.ListStore): + def __init__(self, activities_model, search_text): + gtk.ListStore.__init__(self, gobject.TYPE_STRING, gobject.TYPE_STRING, + gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + + for row in activities_model: + title = row[_COLUMN_TITLE] + address = row[_COLUMN_ADDRESS] + if title.find(search_text) >= 0 or address.find(search_text) >= 0: + self.append([ title, address, row[_COLUMN_SUBTITLE], row[_COLUMN_SERVICE] ]) + + google.LICENSE_KEY = '1As9KaJQFHIJ1L0W5EZPl6vBOFvh/Vaf' + data = google.doGoogleSearch(search_text) + + for result in data.results: + title = result.title + + # FIXME what tags should we actually strip? + title = title.replace('', '') + title = title.replace('', '') + + # FIXME I'm sure there is a better way to + # unescape these. + title = title.replace('"', '"') + title = title.replace('&', '&') + + self.append([ title, result.URL, None, None ]) + +class ActivitiesModel(gtk.ListStore): + def __init__(self): + gtk.ListStore.__init__(self, gobject.TYPE_STRING, gobject.TYPE_STRING, + gobject.TYPE_STRING, gobject.TYPE_PYOBJECT) + + def _filter_dupe_activities(self, model, path, it, user_data): + """Search the list of list rows for an existing service that + has the activity ID we're looking for.""" + helper = user_data + (service, ) = model.get(it, _COLUMN_SERVICE) + if not service: + return False + if service.get_activity_uid() == helper.search_uid: + helper.found = True + return True + return False + + def add_activity(self, buddy, service): + # Web Activity check + activity_uid = service.get_activity_uid() + if activity_uid is None: + return + # Don't show dupes + helper = SearchHelper(activity_uid) + self.foreach(self._filter_dupe_activities, helper) + if helper.found == True: + return + + # Only accept browser activities for now + if service.get_type() == _BROWSER_ACTIVITY_TYPE: + escaped_title = service.get_one_property('Title') + escaped_uri = service.get_one_property('URI') + if escaped_title and escaped_uri: + title = xml.sax.saxutils.unescape(escaped_title) + address = xml.sax.saxutils.unescape(escaped_uri) + subtitle = 'Shared by %s' % buddy.get_nick_name() + self.append([ title, address, subtitle, service ]) + +class ActivitiesView(gtk.TreeView): + def __init__(self, model): + gtk.TreeView.__init__(self, model) + + self._owner = None + + self.set_headers_visible(False) + + theme = gtk.icon_theme_get_default() + size = 48 + self._web_pixbuf = theme.load_icon('emblem-web', size, 0) + self._share_pixbuf = theme.load_icon('emblem-people', size, 0) + + column = gtk.TreeViewColumn('') + self.append_column(column) + + cell = gtk.CellRendererPixbuf() + column.pack_start(cell, False) + column.set_cell_data_func(cell, self._icon_cell_data_func) + + cell = gtk.CellRendererText() + column.pack_start(cell) + column.set_cell_data_func(cell, self._cell_data_func) + + self.connect('row-activated', self._row_activated_cb) + + def _icon_cell_data_func(self, column, cell, model, it): + if model.get_value(it, _COLUMN_SERVICE) == None: + cell.set_property('pixbuf', self._web_pixbuf) + else: + cell.set_property('pixbuf', self._share_pixbuf) + + def _cell_data_func(self, column, cell, model, it): + title = model.get_value(it, _COLUMN_TITLE) + subtitle = model.get_value(it, _COLUMN_SUBTITLE) + if subtitle is None: + subtitle = model.get_value(it, _COLUMN_ADDRESS) + + markup = '' + cgi.escape(title) + '' + markup += '\n' + cgi.escape(subtitle) + + cell.set_property('markup', markup) + cell.set_property('ellipsize', pango.ELLIPSIZE_END) + + def set_owner(self, owner): + self._owner = owner + + def _row_activated_cb(self, treeview, path, column): + bus = dbus.SessionBus() + proxy_obj = bus.get_object('com.redhat.Sugar.Browser', '/com/redhat/Sugar/Browser') + browser_shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.BrowserShell') + + model = self.get_model() + address = model.get_value(model.get_iter(path), _COLUMN_ADDRESS) + service = model.get_value(model.get_iter(path), _COLUMN_SERVICE) + + print 'Activated row %s' % address + + if service is None: + browser_shell.open_browser(address) + else: + if not self._owner: + raise RuntimeError("We don't have an owner yet!") + serialized_service = service.serialize(self._owner) + browser_shell.open_browser(address, serialized_service) + +class StartPage(gtk.HBox): + def __init__(self, ac_signal_object): + gtk.HBox.__init__(self) + + self._ac_signal_object = ac_signal_object + self._ac_signal_object.connect("local-activity-started", + self._on_local_activity_started_cb) + self._ac_signal_object.connect("local-activity-ended", + self._on_local_activity_ended_cb) + + self._pservice = PresenceService.get_instance() + self._pservice.connect("activity-announced", self._on_activity_announced_cb) + self._pservice.connect("new-service-adv", self._on_new_service_adv_cb) + self._pservice.connect("buddy-appeared", self._on_buddy_appeared_cb) + self._pservice.connect("buddy-disappeared", self._on_buddy_disappeared_cb) + self._pservice.start() + self._pservice.track_service_type(_BROWSER_ACTIVITY_TYPE) + if self._pservice.get_owner(): + self._on_buddy_appeared_cb(self._pservice, self._pservice.get_owner()) + + vbox = gtk.VBox() + + search_box = gtk.HBox(False, 6) + search_box.set_border_width(24) + + self._search_entry = gtk.Entry() + self._search_entry.connect('activate', self._search_entry_activate_cb) + search_box.pack_start(self._search_entry) + self._search_entry.show() + + search_button = gtk.Button("Search") + search_button.connect('clicked', self._search_button_clicked_cb) + search_box.pack_start(search_button, False) + search_button.show() + + vbox.pack_start(search_box, False, True) + search_box.show() + + exp_space = gtk.Label('') + vbox.pack_start(exp_space) + exp_space.show() + + self.pack_start(vbox) + vbox.show() + + vbox = gtk.VBox() + + self._search_close_box = gtk.HBox() + + self._search_close_label = gtk.Label() + self._search_close_label.set_alignment(0.0, 0.5) + self._search_close_box.pack_start(self._search_close_label) + self._search_close_label.show() + + close_image = gtk.Image() + close_image.set_from_stock (gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) + close_image.show() + + search_close_button = gtk.Button() + rcstyle = gtk.RcStyle(); + rcstyle.xthickness = rcstyle.ythickness = 0; + search_close_button.modify_style (rcstyle); + search_close_button.add(close_image) + search_close_button.set_relief(gtk.RELIEF_NONE) + search_close_button.set_focus_on_click(False) + search_close_button.connect("clicked", self.__search_close_button_clicked_cb) + + self._search_close_box.pack_start(search_close_button, False) + search_close_button.show() + + vbox.pack_start(self._search_close_box, False) + + sw = gtk.ScrolledWindow() + sw.set_size_request(320, -1) + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + + self._activities_model = ActivitiesModel() + + owner = self._pservice.get_owner() + self._activities = ActivitiesView(self._activities_model) + sw.add(self._activities) + self._activities.show() + + vbox.pack_start(sw) + sw.show() + + self.pack_start(vbox) + vbox.show() + + def __search_close_button_clicked_cb(self, button): + self._search(None) + + def _on_local_activity_started_cb(self, helper, activity_container, activity_id): + print "new local activity %s" % activity_id + + def _on_local_activity_ended_cb(self, helper, activity_container, activity_id): + print "local activity %s disappeared" % activity_id + + def _on_new_service_adv_cb(self, pservice, activity_id, short_stype): + if activity_id: + self._pservice.track_service_type(short_stype) + + def _on_buddy_appeared_cb(self, pservice, buddy): + if buddy.is_owner(): + self._activities.set_owner(buddy) + + def _on_buddy_disappeared_cb(self, pservice, buddy): + if buddy.is_owner(): + self._activities.set_owner(None) + + def _on_activity_announced_cb(self, pservice, service, buddy): + print "Found new activity with type %s" % service.get_full_type() + self._activities_model.add_activity(buddy, service) + if self._activities.get_model() != self._activities_model: + self._search(self._last_search) + + def _search_entry_activate_cb(self, entry): + self._search() + self._search_entry.set_text('') + + def _search_button_clicked_cb(self, button): + self._search() + self._search_entry.set_text('') + + def _search(self, text = None): + if text == None: + text = self._search_entry.get_text() + + if text == None or len(text) == 0: + self._activities.set_model(self._activities_model) + self._search_close_box.hide() + else: + search_model = SearchModel(self._activities_model, text) + self._activities.set_model(search_model) + + self._search_close_label.set_text('Search for %s' % (text)) + self._search_close_box.show() + + self._last_search = text diff --git a/shell/WindowManager.py b/shell/WindowManager.py new file mode 100644 index 0000000..765b2be --- /dev/null +++ b/shell/WindowManager.py @@ -0,0 +1,158 @@ +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +SM_SPACE_PROPORTIONAL = 0 +SM_STEP = 1 + +SLIDING_TIMEOUT = 50 +SLIDING_MODE = SM_SPACE_PROPORTIONAL + +#SLIDING_TIMEOUT = 10 +#SLIDING_MODE = SM_STEP +#SLIDING_STEP = 0.05 + +class WindowManager: + __managers_list = [] + + CENTER = 0 + LEFT = 1 + RIGHT = 2 + TOP = 3 + BOTTOM = 4 + + ABSOLUTE = 0 + SCREEN_RELATIVE = 1 + + def __init__(self, window): + self._window = window + self._sliding_pos = 0 + + WindowManager.__managers_list.append(self) + + window.connect("key-press-event", self.__key_press_event_cb) + + def __key_press_event_cb(self, window, event): + manager = None + + if event.keyval == gtk.keysyms.Left and \ + event.state & gtk.gdk.CONTROL_MASK: + for wm in WindowManager.__managers_list: + if wm._position == WindowManager.LEFT: + manager = wm + + if event.keyval == gtk.keysyms.Up and \ + event.state & gtk.gdk.CONTROL_MASK: + for wm in WindowManager.__managers_list: + if wm._position == WindowManager.TOP: + manager = wm + + if manager and manager._window.get_property('visible'): + manager.slide_window_out() + elif manager: + manager.slide_window_in() + + def set_width(self, width, width_type): + self._width = width + self._width_type = width_type + + def set_height(self, height, height_type): + self._height = height + self._height_type = height_type + + def set_position(self, position): + self._position = position + + def _calc_size_and_position(self): + screen_width = self._window.get_screen().get_width() + screen_height = self._window.get_screen().get_height() + + if self._width_type is WindowManager.ABSOLUTE: + width = self._width + elif self._width_type is WindowManager.SCREEN_RELATIVE: + width = int(screen_width * self._width) + + if self._height_type is WindowManager.ABSOLUTE: + height = self._height + elif self._height_type is WindowManager.SCREEN_RELATIVE: + height = int(screen_height * self._height) + + if self._position is WindowManager.CENTER: + self._x = int((screen_width - width) / 2) + self._y = int((screen_height - height) / 2) + elif self._position is WindowManager.LEFT: + self._x = - int((1.0 - self._sliding_pos) * width) + self._y = int((screen_height - height) / 2) + elif self._position is WindowManager.TOP: + self._x = int((screen_width - width) / 2) + self._y = - int((1.0 - self._sliding_pos) * height) + + self._real_width = width + self._real_height = height + + def _update_size_and_position(self): + self._calc_size_and_position() + self._window.move(self._x, self._y) + self._window.resize(self._real_width, self._real_height) + + def _update_position(self): + self._calc_size_and_position() + self._window.move(self._x, self._y) + + def __slide_in_timeout_cb(self): + if self._sliding_pos == 0: + self._window.show() + + if SLIDING_MODE == SM_SPACE_PROPORTIONAL: + space_to_go = 1.0 - self._sliding_pos + self._sliding_pos += (space_to_go / 2) + else: + self._sliding_pos += SLIDING_STEP + + if self._sliding_pos > .999: + self._sliding_pos = 1.0 + + self._update_position() + + if self._sliding_pos == 1.0: + return False + else: + return True + + def __slide_out_timeout_cb(self): + self._window.show() + + if SLIDING_MODE == SM_SPACE_PROPORTIONAL: + space_to_go = self._sliding_pos + self._sliding_pos -= (space_to_go / 2) + else: + self._sliding_pos -= SLIDING_STEP + + if self._sliding_pos < .001: + self._sliding_pos = 0 + + self._update_position() + + if self._sliding_pos == 0: + self._window.hide() + return False + else: + return True + + def slide_window_in(self): + self._sliding_pos = 0 + gobject.timeout_add(SLIDING_TIMEOUT, self.__slide_in_timeout_cb) + + def slide_window_out(self): + self._sliding_pos = 1.0 + gobject.timeout_add(SLIDING_TIMEOUT, self.__slide_out_timeout_cb) + + def show(self): + self._window.show() + + def update(self): + self._update_size_and_position() + + def manage(self): + self._update_size_and_position() diff --git a/shell/__init__.py b/shell/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/shell/__init__.py diff --git a/shell/google/GoogleSOAPFacade.py b/shell/google/GoogleSOAPFacade.py new file mode 100644 index 0000000..0aab3cf --- /dev/null +++ b/shell/google/GoogleSOAPFacade.py @@ -0,0 +1,85 @@ +""" +Facade that hides the differences between the SOAPpy and SOAP.py +libraries, so that google.py doesn't have to deal with them. + +@author: Brian Landers +@license: Python +@version: 0.5.4 +""" + +import warnings +from distutils.version import LooseVersion + +__author__ = "Brian Landers " +__version__ = "0.6" +__license__ = "Python" + +# +# Wrapper around the python 'warnings' facility +# +def warn( message, level=RuntimeWarning ): + warnings.warn( message, level, stacklevel=3 ) + +# We can't use older version of SOAPpy, due to bugs that break the Google API +minSOAPpyVersion = "0.11.3" + +# +# Try loading SOAPpy first. If that fails, fall back to the old SOAP.py +# +SOAPpy = None +try: + import SOAPpy + from SOAPpy import SOAPProxy, Types + + if LooseVersion( minSOAPpyVersion ) > \ + LooseVersion( SOAPpy.version.__version__ ): + + warn( "Versions of SOAPpy before %s have known bugs that prevent " + + "PyGoogle from functioning." % minSOAPpyVersion ) + raise ImportError + +except ImportError: + warn( "SOAPpy not imported. Trying legacy SOAP.py.", + DeprecationWarning ) + try: + import SOAP + except ImportError: + raise RuntimeError( "Unable to find SOAPpy or SOAP. Can't continue.\n" ) + +# +# Constants that differ between the modules +# +if SOAPpy: + false = Types.booleanType(0) + true = Types.booleanType(1) + structType = Types.structType + faultType = Types.faultType +else: + false = SOAP.booleanType(0) + true = SOAP.booleanType(1) + structType = SOAP.structType + faultType = SOAP.faultType + +# +# Get a SOAP Proxy object in the correct way for the module we're using +# +def getProxy( url, namespace, http_proxy ): + if SOAPpy: + return SOAPProxy( url, + namespace = namespace, + http_proxy = http_proxy ) + + else: + return SOAP.SOAPProxy( url, + namespace = namespace, + http_proxy = http_proxy ) + +# +# Convert an object to a dictionary in the proper way for the module +# we're using for SOAP +# +def toDict( obj ): + if SOAPpy: + return obj._asdict() + else: + return obj._asdict diff --git a/shell/google/Makefile.am b/shell/google/Makefile.am new file mode 100644 index 0000000..89ba29e --- /dev/null +++ b/shell/google/Makefile.am @@ -0,0 +1,5 @@ +cut_n_pastedir = $(datadir)/sugar +cut_n_paste_PYTHON = \ + google.py \ + GoogleSOAPFacade.py \ + SOAP.py diff --git a/shell/google/SOAP.py b/shell/google/SOAP.py new file mode 100755 index 0000000..032a452 --- /dev/null +++ b/shell/google/SOAP.py @@ -0,0 +1,3974 @@ +#!/usr/bin/python +################################################################################ +# +# SOAP.py 0.9.7 - Cayce Ullman (cayce@actzero.com) +# Brian Matthews (blm@actzero.com) +# +# INCLUDED: +# - General SOAP Parser based on sax.xml (requires Python 2.0) +# - General SOAP Builder +# - SOAP Proxy for RPC client code +# - SOAP Server framework for RPC server code +# +# FEATURES: +# - Handles all of the types in the BDG +# - Handles faults +# - Allows namespace specification +# - Allows SOAPAction specification +# - Homogeneous typed arrays +# - Supports multiple schemas +# - Header support (mustUnderstand and actor) +# - XML attribute support +# - Multi-referencing support (Parser/Builder) +# - Understands SOAP-ENC:root attribute +# - Good interop, passes all client tests for Frontier, SOAP::LITE, SOAPRMI +# - Encodings +# - SSL clients (with OpenSSL configured in to Python) +# - SSL servers (with OpenSSL configured in to Python and M2Crypto installed) +# +# TODO: +# - Timeout on method calls - MCU +# - Arrays (sparse, multidimensional and partial) - BLM +# - Clean up data types - BLM +# - Type coercion system (Builder) - MCU +# - Early WSDL Support - MCU +# - Attachments - BLM +# - setup.py - MCU +# - mod_python example - MCU +# - medusa example - MCU +# - Documentation - JAG +# - Look at performance +# +################################################################################ +# +# Copyright (c) 2001, Cayce Ullman. +# Copyright (c) 2001, Brian Matthews. +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# Neither the name of actzero, inc. nor the names of its contributors may +# be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +################################################################################ +# +# Additional changes: +# 0.9.7.3 - 4/18/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# added dump_dict as alias for dump_dictionary for Python 2.2 compatibility +# 0.9.7.2 - 4/12/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# fixed logic to unmarshal the value of "null" attributes ("true" or "1" +# means true, others false) +# 0.9.7.1 - 4/11/2002 - Mark Pilgrim (f8dy@diveintomark.org) +# added "dump_str" as alias for "dump_string" for Python 2.2 compatibility +# Between 2.1 and 2.2, type("").__name__ changed from "string" to "str" +################################################################################ + +import xml.sax +import UserList +import base64 +import cgi +import urllib +import exceptions +import copy +import re +import socket +import string +import sys +import time +import SocketServer +from types import * + +try: from M2Crypto import SSL +except: pass + +ident = '$Id: SOAP.py,v 1.1.1.1 2004/01/16 16:15:18 bluecoat93 Exp $' + +__version__ = "0.9.7.3" + +# Platform hackery + +# Check float support +try: + float("NaN") + float("INF") + float("-INF") + good_float = 1 +except: + good_float = 0 + +################################################################################ +# Exceptions +################################################################################ +class Error(exceptions.Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return "" % self.msg + __repr__ = __str__ + +class RecursionError(Error): + pass + +class UnknownTypeError(Error): + pass + +class HTTPError(Error): + # indicates an HTTP protocol error + def __init__(self, code, msg): + self.code = code + self.msg = msg + def __str__(self): + return "" % (self.code, self.msg) + __repr__ = __str__ + +############################################################################## +# Namespace Class +################################################################################ +def invertDict(dict): + d = {} + + for k, v in dict.items(): + d[v] = k + + return d + +class NS: + XML = "http://www.w3.org/XML/1998/namespace" + + ENV = "http://schemas.xmlsoap.org/soap/envelope/" + ENC = "http://schemas.xmlsoap.org/soap/encoding/" + + XSD = "http://www.w3.org/1999/XMLSchema" + XSD2 = "http://www.w3.org/2000/10/XMLSchema" + XSD3 = "http://www.w3.org/2001/XMLSchema" + + XSD_L = [XSD, XSD2, XSD3] + EXSD_L= [ENC, XSD, XSD2, XSD3] + + XSI = "http://www.w3.org/1999/XMLSchema-instance" + XSI2 = "http://www.w3.org/2000/10/XMLSchema-instance" + XSI3 = "http://www.w3.org/2001/XMLSchema-instance" + XSI_L = [XSI, XSI2, XSI3] + + URN = "http://soapinterop.org/xsd" + + # For generated messages + XML_T = "xml" + ENV_T = "SOAP-ENV" + ENC_T = "SOAP-ENC" + XSD_T = "xsd" + XSD2_T= "xsd2" + XSD3_T= "xsd3" + XSI_T = "xsi" + XSI2_T= "xsi2" + XSI3_T= "xsi3" + URN_T = "urn" + + NSMAP = {ENV_T: ENV, ENC_T: ENC, XSD_T: XSD, XSD2_T: XSD2, + XSD3_T: XSD3, XSI_T: XSI, XSI2_T: XSI2, XSI3_T: XSI3, + URN_T: URN} + NSMAP_R = invertDict(NSMAP) + + STMAP = {'1999': (XSD_T, XSI_T), '2000': (XSD2_T, XSI2_T), + '2001': (XSD3_T, XSI3_T)} + STMAP_R = invertDict(STMAP) + + def __init__(self): + raise Error, "Don't instantiate this" + +################################################################################ +# Configuration class +################################################################################ + +class SOAPConfig: + __readonly = ('SSLserver', 'SSLclient') + + def __init__(self, config = None, **kw): + d = self.__dict__ + + if config: + if not isinstance(config, SOAPConfig): + raise AttributeError, \ + "initializer must be SOAPConfig instance" + + s = config.__dict__ + + for k, v in s.items(): + if k[0] != '_': + d[k] = v + else: + # Setting debug also sets returnFaultInfo, dumpFaultInfo, + # dumpHeadersIn, dumpHeadersOut, dumpSOAPIn, and dumpSOAPOut + self.debug = 0 + # Setting namespaceStyle sets typesNamespace, typesNamespaceURI, + # schemaNamespace, and schemaNamespaceURI + self.namespaceStyle = '1999' + self.strictNamespaces = 0 + self.typed = 1 + self.buildWithNamespacePrefix = 1 + self.returnAllAttrs = 0 + + try: SSL; d['SSLserver'] = 1 + except: d['SSLserver'] = 0 + + try: socket.ssl; d['SSLclient'] = 1 + except: d['SSLclient'] = 0 + + for k, v in kw.items(): + if k[0] != '_': + setattr(self, k, v) + + def __setattr__(self, name, value): + if name in self.__readonly: + raise AttributeError, "readonly configuration setting" + + d = self.__dict__ + + if name in ('typesNamespace', 'typesNamespaceURI', + 'schemaNamespace', 'schemaNamespaceURI'): + + if name[-3:] == 'URI': + base, uri = name[:-3], 1 + else: + base, uri = name, 0 + + if type(value) == StringType: + if NS.NSMAP.has_key(value): + n = (value, NS.NSMAP[value]) + elif NS.NSMAP_R.has_key(value): + n = (NS.NSMAP_R[value], value) + else: + raise AttributeError, "unknown namespace" + elif type(value) in (ListType, TupleType): + if uri: + n = (value[1], value[0]) + else: + n = (value[0], value[1]) + else: + raise AttributeError, "unknown namespace type" + + d[base], d[base + 'URI'] = n + + try: + d['namespaceStyle'] = \ + NS.STMAP_R[(d['typesNamespace'], d['schemaNamespace'])] + except: + d['namespaceStyle'] = '' + + elif name == 'namespaceStyle': + value = str(value) + + if not NS.STMAP.has_key(value): + raise AttributeError, "unknown namespace style" + + d[name] = value + n = d['typesNamespace'] = NS.STMAP[value][0] + d['typesNamespaceURI'] = NS.NSMAP[n] + n = d['schemaNamespace'] = NS.STMAP[value][1] + d['schemaNamespaceURI'] = NS.NSMAP[n] + + elif name == 'debug': + d[name] = \ + d['returnFaultInfo'] = \ + d['dumpFaultInfo'] = \ + d['dumpHeadersIn'] = \ + d['dumpHeadersOut'] = \ + d['dumpSOAPIn'] = \ + d['dumpSOAPOut'] = value + + else: + d[name] = value + +Config = SOAPConfig() + +################################################################################ +# Types and Wrappers +################################################################################ + +class anyType: + _validURIs = (NS.XSD, NS.XSD2, NS.XSD3, NS.ENC) + + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == anyType: + raise Error, "anyType can't be instantiated directly" + + if type(name) in (ListType, TupleType): + self._ns, self._name = name + else: + self._ns, self._name = self._validURIs[0], name + self._typed = typed + self._attrs = {} + + self._cache = None + self._type = self._typeName() + + self._data = self._checkValueSpace(data) + + if attrs != None: + self._setAttrs(attrs) + + def __str__(self): + if self._name: + return "<%s %s at %d>" % (self.__class__, self._name, id(self)) + return "<%s at %d>" % (self.__class__, id(self)) + + __repr__ = __str__ + + def _checkValueSpace(self, data): + return data + + def _marshalData(self): + return str(self._data) + + def _marshalAttrs(self, ns_map, builder): + a = '' + + for attr, value in self._attrs.items(): + ns, n = builder.genns(ns_map, attr[0]) + a += n + ' %s%s="%s"' % \ + (ns, attr[1], cgi.escape(str(value), 1)) + + return a + + def _fixAttr(self, attr): + if type(attr) in (StringType, UnicodeType): + attr = (None, attr) + elif type(attr) == ListType: + attr = tuple(attr) + elif type(attr) != TupleType: + raise AttributeError, "invalid attribute type" + + if len(attr) != 2: + raise AttributeError, "invalid attribute length" + + if type(attr[0]) not in (NoneType, StringType, UnicodeType): + raise AttributeError, "invalid attribute namespace URI type" + + return attr + + def _getAttr(self, attr): + attr = self._fixAttr(attr) + + try: + return self._attrs[attr] + except: + return None + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + self._attrs[attr] = str(value) + + def _setAttrs(self, attrs): + if type(attrs) in (ListType, TupleType): + for i in range(0, len(attrs), 2): + self._setAttr(attrs[i], attrs[i + 1]) + + return + + if type(attrs) == DictType: + d = attrs + elif isinstance(attrs, anyType): + d = attrs._attrs + else: + raise AttributeError, "invalid attribute type" + + for attr, value in d.items(): + self._setAttr(attr, value) + + def _setMustUnderstand(self, val): + self._setAttr((NS.ENV, "mustUnderstand"), val) + + def _getMustUnderstand(self): + return self._getAttr((NS.ENV, "mustUnderstand")) + + def _setActor(self, val): + self._setAttr((NS.ENV, "actor"), val) + + def _getActor(self): + return self._getAttr((NS.ENV, "actor")) + + def _typeName(self): + return self.__class__.__name__[:-4] + + def _validNamespaceURI(self, URI, strict): + if not self._typed: + return None + if URI in self._validURIs: + return URI + if not strict: + return self._ns + raise AttributeError, \ + "not a valid namespace for type %s" % self._type + +class voidType(anyType): + pass + +class stringType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + +class untypedType(stringType): + def __init__(self, data = None, name = None, attrs = None): + stringType.__init__(self, data, name, 0, attrs) + +class IDType(stringType): pass +class NCNameType(stringType): pass +class NameType(stringType): pass +class ENTITYType(stringType): pass +class IDREFType(stringType): pass +class languageType(stringType): pass +class NMTOKENType(stringType): pass +class QNameType(stringType): pass + +class tokenType(anyType): + _validURIs = (NS.XSD2, NS.XSD3) + __invalidre = '[\n\t]|^ | $| ' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class normalizedStringType(anyType): + _validURIs = (NS.XSD3,) + __invalidre = '[\n\r\t]' + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + if type(self.__invalidre) == StringType: + self.__invalidre = re.compile(self.__invalidre) + + if self.__invalidre.search(data): + raise ValueError, "invalid %s value" % self._type + + return data + +class CDATAType(normalizedStringType): + _validURIs = (NS.XSD2,) + +class booleanType(anyType): + def __int__(self): + return self._data + + __nonzero__ = __int__ + + def _marshalData(self): + return ['false', 'true'][self._data] + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if data in (0, '0', 'false', ''): + return 0 + if data in (1, '1', 'true'): + return 1 + raise ValueError, "invalid %s value" % self._type + +class decimalType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType): + raise Error, "invalid %s value" % self._type + + return data + +class floatType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -3.4028234663852886E+38 or \ + data > 3.4028234663852886E+38: + raise ValueError, "invalid %s value" % self._type + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class doubleType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType, FloatType) or \ + data < -1.7976931348623158E+308 or \ + data > 1.7976931348623157E+308: + raise ValueError, "invalid %s value" % self._type + + return data + + def _marshalData(self): + return "%.18g" % self._data # More precision + +class durationType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + try: + # A tuple or a scalar is OK, but make them into a list + + if type(data) == TupleType: + data = list(data) + elif type(data) != ListType: + data = [data] + + if len(data) > 6: + raise Exception, "too many values" + + # Now check the types of all the components, and find + # the first nonzero element along the way. + + f = -1 + + for i in range(len(data)): + if data[i] == None: + data[i] = 0 + continue + + if type(data[i]) not in \ + (IntType, LongType, FloatType): + raise Exception, "element %d a bad type" % i + + if data[i] and f == -1: + f = i + + # If they're all 0, just use zero seconds. + + if f == -1: + self._cache = 'PT0S' + + return (0,) * 6 + + # Make sure only the last nonzero element has a decimal fraction + # and only the first element is negative. + + d = -1 + + for i in range(f, len(data)): + if data[i]: + if d != -1: + raise Exception, \ + "all except the last nonzero element must be " \ + "integers" + if data[i] < 0 and i > f: + raise Exception, \ + "only the first nonzero element can be negative" + elif data[i] != long(data[i]): + d = i + + # Pad the list on the left if necessary. + + if len(data) < 6: + n = 6 - len(data) + f += n + d += n + data = [0] * n + data + + # Save index of the first nonzero element and the decimal + # element for _marshalData. + + self.__firstnonzero = f + self.__decimal = d + + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + t = 0 + + if d[self.__firstnonzero] < 0: + s = '-P' + else: + s = 'P' + + t = 0 + + for i in range(self.__firstnonzero, len(d)): + if d[i]: + if i > 2 and not t: + s += 'T' + t = 1 + if self.__decimal == i: + s += "%g" % abs(d[i]) + else: + s += "%d" % long(abs(d[i])) + s += ['Y', 'M', 'D', 'H', 'M', 'S'][i] + + self._cache = s + + return self._cache + +class timeDurationType(durationType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class dateTimeType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.time() + + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 6: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + cleanDate(data) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dT%02d:%02d:%02d" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + f = d[5] - int(d[5]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class recurringInstantType(anyType): + _validURIs = (NS.XSD,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = list(time.gmtime(time.time())[:6]) + if (type(data) in (IntType, LongType)): + data = list(time.gmtime(data)[:6]) + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[:6]) + data[5] += f + elif type(data) in (ListType, TupleType): + if len(data) < 1: + raise Exception, "not enough values" + if len(data) > 9: + raise Exception, "too many values" + + data = list(data[:6]) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + f = len(data) + + for i in range(f): + if data[i] == None: + if f < i: + raise Exception, \ + "only leftmost elements can be none" + else: + f = i + break + + cleanDate(data, f) + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + e = list(d) + neg = '' + + if e[0] < 0: + neg = '-' + e[0] = abs(e[0]) + + if not e[0]: + e[0] = '--' + elif e[0] < 100: + e[0] = '-' + "%02d" % e[0] + else: + e[0] = "%04d" % e[0] + + for i in range(1, len(e)): + if e[i] == None or (i < 3 and e[i] == 0): + e[i] = '-' + else: + if e[i] < 0: + neg = '-' + e[i] = abs(e[i]) + + e[i] = "%02d" % e[i] + + if d[5]: + f = abs(d[5] - int(d[5])) + + if f: + e[5] += ("%g" % f)[1:] + + s = "%s%s-%s-%sT%s:%s:%sZ" % ((neg,) + tuple(e)) + + self._cache = s + + return self._cache + +class timeInstantType(dateTimeType): + _validURIs = (NS.XSD, NS.XSD2, NS.ENC) + +class timePeriodType(dateTimeType): + _validURIs = (NS.XSD2, NS.ENC) + +class timeType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[3:6] + elif (type(data) == FloatType): + f = data - int(data) + data = list(time.gmtime(int(data))[3:6]) + data[2] += f + elif type(data) in (IntType, LongType): + data = time.gmtime(data)[3:6] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[3:6] + elif len(data) > 3: + raise Exception, "too many values" + + data = [None, None, None] + list(data) + + if len(data) < 6: + data += [0] * (6 - len(data)) + + cleanDate(data, 3) + + data = data[3:] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = '' + + s = time.strftime("%H:%M:%S", (0, 0, 0) + d + (0, 0, -1)) + f = d[2] - int(d[2]) + if f != 0: + s += ("%g" % f)[1:] + s += 'Z' + + self._cache = s + + return self._cache + +class dateType(anyType): + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:3] + elif len(data) > 3: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 3: + data += [1, 1, 1][len(data):] + + data += [0, 0, 0] + + cleanDate(data) + + data = data[:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:2] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[0:2] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data += [1, 0, 0, 0] + + cleanDate(data) + + data = data[:2] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04d-%02dZ" % ((abs(d[0]),) + d[1:]) + if d[0] < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class gYearType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%04dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class centuryType(anyType): + _validURIs = (NS.XSD2, NS.ENC) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[0:1] / 100 + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:1] / 100 + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + d = self._data + s = "%02dZ" % abs(d) + if d < 0: + s = '-' + s + + self._cache = s + + return self._cache + +class yearType(gYearType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:3] + elif type(data) in (IntType, LongType, FloatType): + data = time.gmtime(data)[1:3] + elif type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[0:2] + elif len(data) > 2: + raise Exception, "too many values" + + data = list(data) + + if len(data) < 2: + data += [1, 1][len(data):] + + data = [0] + data + [0, 0, 0] + + cleanDate(data, 1) + + data = data[1:3] + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return tuple(data) + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d-%02dZ" % self._data + + return self._cache + +class recurringDateType(gMonthDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class gMonthType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[1:2] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[1:2] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 12: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "--%02d--Z" % self._data + + return self._cache + +class monthType(gMonthType): + _validURIs = (NS.XSD2, NS.ENC) + +class gDayType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + try: + if data == None: + data = time.gmtime(time.time())[2:3] + elif type(data) in (IntType, LongType, FloatType): + data = [data] + + if type(data) in (ListType, TupleType): + if len(data) == 9: + data = data[2:3] + elif len(data) < 1: + raise Exception, "too few values" + elif len(data) > 1: + raise Exception, "too many values" + + if type(data[0]) == FloatType: + try: s = int(data[0]) + except: s = long(data[0]) + + if s != data[0]: + raise Exception, "not integral" + + data = [s] + elif type(data[0]) not in (IntType, LongType): + raise Exception, "bad type" + + if data[0] < 1 or data[0] > 31: + raise Exception, "bad value" + else: + raise Exception, "invalid type" + except Exception, e: + raise ValueError, "invalid %s value - %s" % (self._type, e) + + return data[0] + + def _marshalData(self): + if self._cache == None: + self._cache = "---%02dZ" % self._data + + return self._cache + +class recurringDayType(gDayType): + _validURIs = (NS.XSD2, NS.ENC) + +class hexBinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = encodeHexString(self._data) + + return self._cache + +class base64BinaryType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = base64.encodestring(self._data) + + return self._cache + +class base64Type(base64BinaryType): + _validURIs = (NS.ENC,) + +class binaryType(anyType): + _validURIs = (NS.XSD, NS.ENC) + + def __init__(self, data, name = None, typed = 1, encoding = 'base64', + attrs = None): + + anyType.__init__(self, data, name, typed, attrs) + + self._setAttr('encoding', encoding) + + def _marshalData(self): + if self._cache == None: + if self._getAttr((None, 'encoding')) == 'base64': + self._cache = base64.encodestring(self._data) + else: + self._cache = encodeHexString(self._data) + + return self._cache + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _setAttr(self, attr, value): + attr = self._fixAttr(attr) + + if attr[1] == 'encoding': + if attr[0] != None or value not in ('base64', 'hex'): + raise AttributeError, "invalid encoding" + + self._cache = None + + anyType._setAttr(self, attr, value) + + +class anyURIType(anyType): + _validURIs = (NS.XSD3,) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (StringType, UnicodeType): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + if self._cache == None: + self._cache = urllib.quote(self._data) + + return self._cache + +class uriType(anyURIType): + _validURIs = (NS.XSD,) + +class uriReferenceType(anyURIType): + _validURIs = (NS.XSD2,) + +class NOTATIONType(anyType): + def __init__(self, data, name = None, typed = 1, attrs = None): + + if self.__class__ == NOTATIONType: + raise Error, "a NOTATION can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + +class ENTITIESType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) in (StringType, UnicodeType): + return (data,) + + if type(data) not in (ListType, TupleType) or \ + filter (lambda x: type(x) not in (StringType, UnicodeType), data): + raise AttributeError, "invalid %s type" % self._type + + return data + + def _marshalData(self): + return ' '.join(self._data) + +class IDREFSType(ENTITIESType): pass +class NMTOKENSType(ENTITIESType): pass + +class integerType(anyType): + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType): + raise ValueError, "invalid %s value" % self._type + + return data + +class nonPositiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data > 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Positive_IntegerType(nonPositiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-positive-integer' + +class negativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data >= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class negative_IntegerType(negativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'negative-integer' + +class longType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -9223372036854775808L or \ + data > 9223372036854775807L: + raise ValueError, "invalid %s value" % self._type + + return data + +class intType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -2147483648L or \ + data > 2147483647: + raise ValueError, "invalid %s value" % self._type + + return data + +class shortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -32768 or \ + data > 32767: + raise ValueError, "invalid %s value" % self._type + + return data + +class byteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < -128 or \ + data > 127: + raise ValueError, "invalid %s value" % self._type + + return data + +class nonNegativeIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data < 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class non_Negative_IntegerType(nonNegativeIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'non-negative-integer' + +class unsignedLongType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 18446744073709551615L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedIntType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 4294967295L: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedShortType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 65535: + raise ValueError, "invalid %s value" % self._type + + return data + +class unsignedByteType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or \ + data < 0 or \ + data > 255: + raise ValueError, "invalid %s value" % self._type + + return data + +class positiveIntegerType(anyType): + _validURIs = (NS.XSD2, NS.XSD3, NS.ENC) + + def _checkValueSpace(self, data): + if data == None: + raise ValueError, "must supply initial %s value" % self._type + + if type(data) not in (IntType, LongType) or data <= 0: + raise ValueError, "invalid %s value" % self._type + + return data + +class positive_IntegerType(positiveIntegerType): + _validURIs = (NS.XSD,) + + def _typeName(self): + return 'positive-integer' + +# Now compound types + +class compoundType(anyType): + def __init__(self, data = None, name = None, typed = 1, attrs = None): + if self.__class__ == compoundType: + raise Error, "a compound can't be instantiated directly" + + anyType.__init__(self, data, name, typed, attrs) + self._aslist = [] + self._asdict = {} + self._keyord = [] + + if type(data) == DictType: + self.__dict__.update(data) + + def __getitem__(self, item): + if type(item) == IntType: + return self._aslist[item] + return getattr(self, item) + + def __len__(self): + return len(self._aslist) + + def __nonzero__(self): + return 1 + + def _keys(self): + return filter(lambda x: x[0] != '_', self.__dict__.keys()) + + def _addItem(self, name, value, attrs = None): + d = self._asdict + + if d.has_key(name): + if type(d[name]) != ListType: + d[name] = [d[name]] + d[name].append(value) + else: + d[name] = value + + self._keyord.append(name) + self._aslist.append(value) + self.__dict__[name] = d[name] + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + d = self._asdict + + if subpos == 0 and type(d[name]) != ListType: + d[name] = value + else: + d[name][subpos] = value + + self._keyord[pos] = name + self._aslist[pos] = value + self.__dict__[name] = d[name] + + def _getItemAsList(self, name, default = []): + try: + d = self.__dict__[name] + except: + return default + + if type(d) == ListType: + return d + return [d] + +class structType(compoundType): + pass + +class headerType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Header", typed, attrs) + +class bodyType(structType): + _validURIs = (NS.ENV,) + + def __init__(self, data = None, typed = 1, attrs = None): + structType.__init__(self, data, "Body", typed, attrs) + +class arrayType(UserList.UserList, compoundType): + def __init__(self, data = None, name = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + if data: + if type(data) not in (ListType, TupleType): + raise Error, "Data must be a sequence" + + UserList.UserList.__init__(self, data) + compoundType.__init__(self, data, name, 0, attrs) + + self._elemsname = elemsname or "item" + + if data == None: + self._rank = rank + + # According to 5.4.2.2 in the SOAP spec, each element in a + # sparse array must have a position. _posstate keeps track of + # whether we've seen a position or not. It's possible values + # are: + # -1 No elements have been added, so the state is indeterminate + # 0 An element without a position has been added, so no + # elements can have positions + # 1 An element with a position has been added, so all elements + # must have positions + + self._posstate = -1 + + self._full = 0 + + if asize in ('', None): + asize = '0' + + self._dims = map (lambda x: int(x), str(asize).split(',')) + self._dims.reverse() # It's easier to work with this way + self._poss = [0] * len(self._dims) # This will end up + # reversed too + + for i in range(len(self._dims)): + if self._dims[i] < 0 or \ + self._dims[i] == 0 and len(self._dims) > 1: + raise TypeError, "invalid Array dimensions" + + if offset > 0: + self._poss[i] = offset % self._dims[i] + offset = int(offset / self._dims[i]) + + # Don't break out of the loop if offset is 0 so we test all the + # dimensions for > 0. + if offset: + raise AttributeError, "invalid Array offset" + + a = [None] * self._dims[0] + + for i in range(1, len(self._dims)): + b = [] + + for j in range(self._dims[i]): + b.append(copy.deepcopy(a)) + + a = b + + self.data = a + + def _addItem(self, name, value, attrs): + if self._full: + raise ValueError, "Array is full" + + pos = attrs.get((NS.ENC, 'position')) + + if pos != None: + if self._posstate == 0: + raise AttributeError, \ + "all elements in a sparse Array must have a " \ + "position attribute" + + self._posstate = 1 + + try: + if pos[0] == '[' and pos[-1] == ']': + pos = map (lambda x: int(x), pos[1:-1].split(',')) + pos.reverse() + + if len(pos) == 1: + pos = pos[0] + + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if pos: + raise Exception + elif len(pos) != len(self._dims): + raise Exception + else: + for i in range(len(self._dims)): + if pos[i] >= self._dims[i]: + raise Exception + + curpos = pos + else: + raise Exception + except: + raise AttributeError, \ + "invalid Array element position %s" % str(pos) + else: + if self._posstate == 1: + raise AttributeError, \ + "only elements in a sparse Array may have a " \ + "position attribute" + + self._posstate = 0 + + curpos = self._poss + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + + if pos == None: + self._poss[0] += 1 + + for i in range(len(self._dims) - 1): + if self._poss[i] < self._dims[i]: + break + + self._poss[i] = 0 + self._poss[i + 1] += 1 + + if self._dims[-1] and self._poss[-1] >= self._dims[-1]: + self._full = 1 + + def _placeItem(self, name, value, pos, subpos, attrs = None): + curpos = [0] * len(self._dims) + + for i in range(len(self._dims)): + if self._dims[i] == 0: + curpos[0] = pos + break + + curpos[i] = pos % self._dims[i] + pos = int(pos / self._dims[i]) + + if pos == 0: + break + + if self._dims[i] != 0 and pos: + raise Error, "array index out of range" + + a = self.data + + for i in range(len(self._dims) - 1, 0, -1): + a = a[curpos[i]] + + if curpos[0] >= len(a): + a += [None] * (len(a) - curpos[0] + 1) + + a[curpos[0]] = value + +class typedArrayType(arrayType): + def __init__(self, data = None, name = None, typed = None, attrs = None, + offset = 0, rank = None, asize = 0, elemsname = None): + + arrayType.__init__(self, data, name, attrs, offset, rank, asize, + elemsname) + + self._type = typed + +class faultType(structType, Error): + def __init__(self, faultcode = "", faultstring = "", detail = None): + self.faultcode = faultcode + self.faultstring = faultstring + if detail != None: + self.detail = detail + + structType.__init__(self, None, 0) + + def _setDetail(self, detail = None): + if detail != None: + self.detail = detail + else: + try: del self.detail + except AttributeError: pass + + def __repr__(self): + return "" % (self.faultcode, self.faultstring) + + __str__ = __repr__ + +################################################################################ +class RefHolder: + def __init__(self, name, frame): + self.name = name + self.parent = frame + self.pos = len(frame) + self.subpos = frame.namecounts.get(name, 0) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + +################################################################################ +# Utility infielders +################################################################################ +def collapseWhiteSpace(s): + return re.sub('\s+', ' ', s).strip() + +def decodeHexString(data): + conv = {'0': 0x0, '1': 0x1, '2': 0x2, '3': 0x3, '4': 0x4, + '5': 0x5, '6': 0x6, '7': 0x7, '8': 0x8, '9': 0x9, 'a': 0xa, + 'b': 0xb, 'c': 0xc, 'd': 0xd, 'e': 0xe, 'f': 0xf, 'A': 0xa, + 'B': 0xb, 'C': 0xc, 'D': 0xd, 'E': 0xe, 'F': 0xf,} + ws = string.whitespace + + bin = '' + + i = 0 + + while i < len(data): + if data[i] not in ws: + break + i += 1 + + low = 0 + + while i < len(data): + c = data[i] + + if c in string.whitespace: + break + + try: + c = conv[c] + except KeyError: + raise ValueError, \ + "invalid hex string character `%s'" % c + + if low: + bin += chr(high * 16 + c) + low = 0 + else: + high = c + low = 1 + + i += 1 + + if low: + raise ValueError, "invalid hex string length" + + while i < len(data): + if data[i] not in string.whitespace: + raise ValueError, \ + "invalid hex string character `%s'" % c + + i += 1 + + return bin + +def encodeHexString(data): + h = '' + + for i in data: + h += "%02X" % ord(i) + + return h + +def leapMonth(year, month): + return month == 2 and \ + year % 4 == 0 and \ + (year % 100 != 0 or year % 400 == 0) + +def cleanDate(d, first = 0): + ranges = (None, (1, 12), (1, 31), (0, 23), (0, 59), (0, 61)) + months = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + names = ('year', 'month', 'day', 'hours', 'minutes', 'seconds') + + if len(d) != 6: + raise ValueError, "date must have 6 elements" + + for i in range(first, 6): + s = d[i] + + if type(s) == FloatType: + if i < 5: + try: + s = int(s) + except OverflowError: + if i > 0: + raise + s = long(s) + + if s != d[i]: + raise ValueError, "%s must be integral" % names[i] + + d[i] = s + elif type(s) == LongType: + try: s = int(s) + except: pass + elif type(s) != IntType: + raise TypeError, "%s isn't a valid type" % names[i] + + if i == first and s < 0: + continue + + if ranges[i] != None and \ + (s < ranges[i][0] or ranges[i][1] < s): + raise ValueError, "%s out of range" % names[i] + + if first < 6 and d[5] >= 61: + raise ValueError, "seconds out of range" + + if first < 2: + leap = first < 1 and leapMonth(d[0], d[1]) + + if d[2] > months[d[1]] + leap: + raise ValueError, "day out of range" + +class UnderflowError(exceptions.ArithmeticError): + pass + +def debugHeader(title): + s = '*** ' + title + ' ' + print s + ('*' * (72 - len(s))) + +def debugFooter(title): + print '*' * 72 + sys.stdout.flush() + +################################################################################ +# SOAP Parser +################################################################################ +class SOAPParser(xml.sax.handler.ContentHandler): + class Frame: + def __init__(self, name, kind = None, attrs = {}, rules = {}): + self.name = name + self.kind = kind + self.attrs = attrs + self.rules = rules + + self.contents = [] + self.names = [] + self.namecounts = {} + self.subattrs = [] + + def append(self, name, data, attrs): + self.names.append(name) + self.contents.append(data) + self.subattrs.append(attrs) + + if self.namecounts.has_key(name): + self.namecounts[name] += 1 + else: + self.namecounts[name] = 1 + + def _placeItem(self, name, value, pos, subpos = 0, attrs = None): + self.contents[pos] = value + + if attrs: + self.attrs.update(attrs) + + def __len__(self): + return len(self.contents) + + def __repr__(self): + return "<%s %s at %d>" % (self.__class__, self.name, id(self)) + + def __init__(self, rules = None): + xml.sax.handler.ContentHandler.__init__(self) + self.body = None + self.header = None + self.attrs = {} + self._data = None + self._next = "E" # Keeping state for message validity + self._stack = [self.Frame('SOAP')] + + # Make two dictionaries to store the prefix <-> URI mappings, and + # initialize them with the default + self._prem = {NS.XML_T: NS.XML} + self._prem_r = {NS.XML: NS.XML_T} + self._ids = {} + self._refs = {} + self._rules = rules + + def startElementNS(self, name, qname, attrs): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + name = (None, name[1][1:]) + else: + name = tuple(name) + + # First some checking of the layout of the message + + if self._next == "E": + if name[1] != 'Envelope': + raise Error, "expected `SOAP-ENV:Envelope', got `%s:%s'" % \ + (self._prem_r[name[0]], name[1]) + if name[0] != NS.ENV: + raise faultType, ("%s:VersionMismatch" % NS.ENV_T, + "Don't understand version `%s' Envelope" % name[0]) + else: + self._next = "HorB" + elif self._next == "HorB": + if name[0] == NS.ENV and name[1] in ("Header", "Body"): + self._next = None + else: + raise Error, \ + "expected `SOAP-ENV:Header' or `SOAP-ENV:Body', " \ + "got `%s'" % self._prem_r[name[0]] + ':' + name[1] + elif self._next == "B": + if name == (NS.ENV, "Body"): + self._next = None + else: + raise Error, "expected `SOAP-ENV:Body', got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + elif self._next == "": + raise Error, "expected nothing, got `%s'" % \ + self._prem_r[name[0]] + ':' + name[1] + + if len(self._stack) == 2: + rules = self._rules + else: + try: + rules = self._stack[-1].rules[name[1]] + except: + rules = None + + if type(rules) not in (NoneType, DictType): + kind = rules + else: + kind = attrs.get((NS.ENC, 'arrayType')) + + if kind != None: + del attrs._attrs[(NS.ENC, 'arrayType')] + + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: + kind = None + + self.pushFrame(self.Frame(name[1], kind, attrs._attrs, rules)) + + self._data = '' # Start accumulating + + def pushFrame(self, frame): + self._stack.append(frame) + + def popFrame(self): + return self._stack.pop() + + def endElementNS(self, name, qname): + # Workaround two sax bugs + if name[0] == None and name[1][0] == ' ': + ns, name = None, name[1][1:] + else: + ns, name = tuple(name) + + if self._next == "E": + raise Error, "didn't get SOAP-ENV:Envelope" + if self._next in ("HorB", "B"): + raise Error, "didn't get SOAP-ENV:Body" + + cur = self.popFrame() + attrs = cur.attrs + + idval = None + + if attrs.has_key((None, 'id')): + idval = attrs[(None, 'id')] + + if self._ids.has_key(idval): + raise Error, "duplicate id `%s'" % idval + + del attrs[(None, 'id')] + + root = 1 + + if len(self._stack) == 3: + if attrs.has_key((NS.ENC, 'root')): + root = int(attrs[(NS.ENC, 'root')]) + + # Do some preliminary checks. First, if root="0" is present, + # the element must have an id. Next, if root="n" is present, + # n something other than 0 or 1, raise an exception. + + if root == 0: + if idval == None: + raise Error, "non-root element must have an id" + elif root != 1: + raise Error, "SOAP-ENC:root must be `0' or `1'" + + del attrs[(NS.ENC, 'root')] + + while 1: + href = attrs.get((None, 'href')) + if href: + if href[0] != '#': + raise Error, "only do local hrefs right now" + if self._data != None and self._data.strip() != '': + raise Error, "hrefs can't have data" + + href = href[1:] + + if self._ids.has_key(href): + data = self._ids[href] + else: + data = RefHolder(name, self._stack[-1]) + + if self._refs.has_key(href): + self._refs[href].append(data) + else: + self._refs[href] = [data] + + del attrs[(None, 'href')] + + break + + kind = None + + if attrs: + for i in NS.XSI_L: + if attrs.has_key((i, 'type')): + kind = attrs[(i, 'type')] + del attrs[(i, 'type')] + + if kind != None: + i = kind.find(':') + if i >= 0: + kind = (self._prem[kind[:i]], kind[i + 1:]) + else: +# XXX What to do here? (None, kind) is just going to fail in convertType + kind = (None, kind) + + null = 0 + + if attrs: + for i in (NS.XSI, NS.XSI2): + if attrs.has_key((i, 'null')): + null = attrs[(i, 'null')] + del attrs[(i, 'null')] + + if attrs.has_key((NS.XSI3, 'nil')): + null = attrs[(NS.XSI3, 'nil')] + del attrs[(NS.XSI3, 'nil')] + + #MAP 4/12/2002 - must also support "true" + #null = int(null) + null = (str(null).lower() in ['true', '1']) + + if null: + if len(cur) or \ + (self._data != None and self._data.strip() != ''): + raise Error, "nils can't have data" + + data = None + + break + + if len(self._stack) == 2: + if (ns, name) == (NS.ENV, "Header"): + self.header = data = headerType(attrs = attrs) + self._next = "B" + break + elif (ns, name) == (NS.ENV, "Body"): + self.body = data = bodyType(attrs = attrs) + self._next = "" + break + elif len(self._stack) == 3 and self._next == None: + if (ns, name) == (NS.ENV, "Fault"): + data = faultType() + self._next = "" + break + + if cur.rules != None: + rule = cur.rules + + if type(rule) in (StringType, UnicodeType): +# XXX Need a namespace here + rule = (None, rule) + elif type(rule) == ListType: + rule = tuple(rule) + +# XXX What if rule != kind? + if callable(rule): + data = rule(self._data) + elif type(rule) == DictType: + data = structType(name = (ns, name), attrs = attrs) + else: + data = self.convertType(self._data, rule, attrs) + + break + + if (kind == None and cur.kind != None) or \ + (kind == (NS.ENC, 'Array')): + kind = cur.kind + + if kind == None: + kind = 'ur-type[%d]' % len(cur) + else: + kind = kind[1] + + if len(cur.namecounts) == 1: + elemsname = cur.names[0] + else: + elemsname = None + + data = self.startArray((ns, name), kind, attrs, elemsname) + + break + + if len(self._stack) == 3 and kind == None and \ + len(cur) == 0 and \ + (self._data == None or self._data.strip() == ''): + data = structType(name = (ns, name), attrs = attrs) + break + + if len(cur) == 0 and ns != NS.URN: + # Nothing's been added to the current frame so it must be a + # simple type. + + if kind == None: + # If the current item's container is an array, it will + # have a kind. If so, get the bit before the first [, + # which is the type of the array, therefore the type of + # the current item. + + kind = self._stack[-1].kind + + if kind != None: + i = kind[1].find('[') + if i >= 0: + kind = (kind[0], kind[1][:i]) + elif ns != None: + kind = (ns, name) + + if kind != None: + try: + data = self.convertType(self._data, kind, attrs) + except UnknownTypeError: + data = None + else: + data = None + + if data == None: + data = self._data or '' + + if len(attrs) == 0: + try: data = str(data) + except: pass + + break + + data = structType(name = (ns, name), attrs = attrs) + + break + + if isinstance(data, compoundType): + for i in range(len(cur)): + v = cur.contents[i] + data._addItem(cur.names[i], v, cur.subattrs[i]) + + if isinstance(v, RefHolder): + v.parent = data + + if root: + self._stack[-1].append(name, data, attrs) + + if idval != None: + self._ids[idval] = data + + if self._refs.has_key(idval): + for i in self._refs[idval]: + i.parent._placeItem(i.name, data, i.pos, i.subpos, attrs) + + del self._refs[idval] + + self.attrs[id(data)] = attrs + + if isinstance(data, anyType): + data._setAttrs(attrs) + + self._data = None # Stop accumulating + + def endDocument(self): + if len(self._refs) == 1: + raise Error, \ + "unresolved reference " + self._refs.keys()[0] + elif len(self._refs) > 1: + raise Error, \ + "unresolved references " + ', '.join(self._refs.keys()) + + def startPrefixMapping(self, prefix, uri): + self._prem[prefix] = uri + self._prem_r[uri] = prefix + + def endPrefixMapping(self, prefix): + try: + del self._prem_r[self._prem[prefix]] + del self._prem[prefix] + except: + pass + + def characters(self, c): + if self._data != None: + self._data += c + + arrayre = '^(?:(?P[^:]*):)?' \ + '(?P[^[]+)' \ + '(?:\[(?P,*)\])?' \ + '(?:\[(?P\d+(?:,\d+)*)?\])$' + + def startArray(self, name, kind, attrs, elemsname): + if type(self.arrayre) == StringType: + self.arrayre = re.compile (self.arrayre) + + offset = attrs.get((NS.ENC, "offset")) + + if offset != None: + del attrs[(NS.ENC, "offset")] + + try: + if offset[0] == '[' and offset[-1] == ']': + offset = int(offset[1:-1]) + if offset < 0: + raise Exception + else: + raise Exception + except: + raise AttributeError, "invalid Array offset" + else: + offset = 0 + + try: + m = self.arrayre.search(kind) + + if m == None: + raise Exception + + t = m.group('type') + + if t == 'ur-type': + return arrayType(None, name, attrs, offset, m.group('rank'), + m.group('asize'), elemsname) + elif m.group('ns') != None: + return typedArrayType(None, name, + (self._prem[m.group('ns')], t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + else: + return typedArrayType(None, name, (None, t), attrs, offset, + m.group('rank'), m.group('asize'), elemsname) + except: + raise AttributeError, "invalid Array type `%s'" % kind + + # Conversion + + class DATETIMECONSTS: + SIGNre = '(?P-?)' + CENTURYre = '(?P\d{2,})' + YEARre = '(?P\d{2})' + MONTHre = '(?P\d{2})' + DAYre = '(?P\d{2})' + HOURre = '(?P\d{2})' + MINUTEre = '(?P\d{2})' + SECONDre = '(?P\d{2}(?:\.\d*)?)' + TIMEZONEre = '(?PZ)|(?P[-+])(?P\d{2}):' \ + '(?P\d{2})' + BOSre = '^\s*' + EOSre = '\s*$' + + __allres = {'sign': SIGNre, 'century': CENTURYre, 'year': YEARre, + 'month': MONTHre, 'day': DAYre, 'hour': HOURre, + 'minute': MINUTEre, 'second': SECONDre, 'timezone': TIMEZONEre, + 'b': BOSre, 'e': EOSre} + + dateTime = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)sT' \ + '%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % __allres + timeInstant = dateTime + timePeriod = dateTime + time = '%(b)s%(hour)s:%(minute)s:%(second)s(%(timezone)s)?%(e)s' % \ + __allres + date = '%(b)s%(sign)s%(century)s%(year)s-%(month)s-%(day)s' \ + '(%(timezone)s)?%(e)s' % __allres + century = '%(b)s%(sign)s%(century)s(%(timezone)s)?%(e)s' % __allres + gYearMonth = '%(b)s%(sign)s%(century)s%(year)s-%(month)s' \ + '(%(timezone)s)?%(e)s' % __allres + gYear = '%(b)s%(sign)s%(century)s%(year)s(%(timezone)s)?%(e)s' % \ + __allres + year = gYear + gMonthDay = '%(b)s--%(month)s-%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDate = gMonthDay + gDay = '%(b)s---%(day)s(%(timezone)s)?%(e)s' % __allres + recurringDay = gDay + gMonth = '%(b)s--%(month)s--(%(timezone)s)?%(e)s' % __allres + month = gMonth + + recurringInstant = '%(b)s%(sign)s(%(century)s|-)(%(year)s|-)-' \ + '(%(month)s|-)-(%(day)s|-)T' \ + '(%(hour)s|-):(%(minute)s|-):(%(second)s|-)' \ + '(%(timezone)s)?%(e)s' % __allres + + duration = '%(b)s%(sign)sP' \ + '((?P\d+)Y)?' \ + '((?P\d+)M)?' \ + '((?P\d+)D)?' \ + '((?PT)' \ + '((?P\d+)H)?' \ + '((?P\d+)M)?' \ + '((?P\d*(?:\.\d*)?)S)?)?%(e)s' % \ + __allres + + timeDuration = duration + + # The extra 31 on the front is: + # - so the tuple is 1-based + # - so months[month-1] is December's days if month is 1 + + months = (31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + + def convertDateTime(self, value, kind): + def getZoneOffset(d): + zoffs = 0 + + try: + if d['zulu'] == None: + zoffs = 60 * int(d['tzhour']) + int(d['tzminute']) + if d['tzsign'] != '-': + zoffs = -zoffs + except TypeError: + pass + + return zoffs + + def applyZoneOffset(months, zoffs, date, minfield, posday = 1): + if zoffs == 0 and (minfield > 4 or 0 <= date[5] < 60): + return date + + if minfield > 5: date[5] = 0 + if minfield > 4: date[4] = 0 + + if date[5] < 0: + date[4] += int(date[5]) / 60 + date[5] %= 60 + + date[4] += zoffs + + if minfield > 3 or 0 <= date[4] < 60: return date + + date[3] += date[4] / 60 + date[4] %= 60 + + if minfield > 2 or 0 <= date[3] < 24: return date + + date[2] += date[3] / 24 + date[3] %= 24 + + if minfield > 1: + if posday and date[2] <= 0: + date[2] += 31 # zoffs is at most 99:59, so the + # day will never be less than -3 + return date + + while 1: + # The date[1] == 3 (instead of == 2) is because we're + # going back a month, so we need to know if the previous + # month is February, so we test if this month is March. + + leap = minfield == 0 and date[1] == 3 and \ + date[0] % 4 == 0 and \ + (date[0] % 100 != 0 or date[0] % 400 == 0) + + if 0 < date[2] <= months[date[1]] + leap: break + + date[2] += months[date[1] - 1] + leap + + date[1] -= 1 + + if date[1] > 0: break + + date[1] = 12 + + if minfield > 0: break + + date[0] -= 1 + + return date + + try: + exp = getattr(self.DATETIMECONSTS, kind) + except AttributeError: + return None + + if type(exp) == StringType: + exp = re.compile(exp) + setattr (self.DATETIMECONSTS, kind, exp) + + m = exp.search(value) + + try: + if m == None: + raise Exception + + d = m.groupdict() + f = ('century', 'year', 'month', 'day', + 'hour', 'minute', 'second') + fn = len(f) # Index of first non-None value + r = [] + + if kind in ('duration', 'timeDuration'): + if d['sep'] != None and d['hour'] == None and \ + d['minute'] == None and d['second'] == None: + raise Exception + + f = f[1:] + + for i in range(len(f)): + s = d[f[i]] + + if s != None: + if f[i] == 'second': + s = float(s) + else: + try: s = int(s) + except ValueError: s = long(s) + + if i < fn: fn = i + + r.append(s) + + if fn > len(r): # Any non-Nones? + raise Exception + + if d['sign'] == '-': + r[fn] = -r[fn] + + return tuple(r) + + if kind == 'recurringInstant': + for i in range(len(f)): + s = d[f[i]] + + if s == None or s == '-': + if i > fn: + raise Exception + s = None + else: + if i < fn: + fn = i + + if f[i] == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + r.append(s) + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if fn < len(r) and d['sign'] == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + return tuple(applyZoneOffset(self.DATETIMECONSTS.months, + getZoneOffset(d), r, fn, 0)) + + r = [0, 0, 1, 1, 0, 0, 0] + + for i in range(len(f)): + field = f[i] + + s = d.get(field) + + if s != None: + if field == 'second': + s = float(s) + else: + try: + s = int(s) + except ValueError: + s = long(s) + + if i < fn: + fn = i + + r[i] = s + + if fn > len(r): # Any non-Nones? + raise Exception + + s = r.pop(0) + + if fn == 0: + r[0] += s * 100 + else: + fn -= 1 + + if d.get('sign') == '-': + r[fn] = -r[fn] + + cleanDate(r, fn) + + zoffs = getZoneOffset(d) + + if zoffs: + r = applyZoneOffset(self.DATETIMECONSTS.months, zoffs, r, fn) + + if kind == 'century': + return r[0] / 100 + + s = [] + + for i in range(1, len(f)): + if d.has_key(f[i]): + s.append(r[i - 1]) + + if len(s) == 1: + return s[0] + return tuple(s) + except Exception, e: + raise Error, "invalid %s value `%s' - %s" % (kind, value, e) + + intlimits = \ + { + 'nonPositiveInteger': (0, None, 0), + 'non-positive-integer': (0, None, 0), + 'negativeInteger': (0, None, -1), + 'negative-integer': (0, None, -1), + 'long': (1, -9223372036854775808L, + 9223372036854775807L), + 'int': (0, -2147483648L, 2147483647), + 'short': (0, -32768, 32767), + 'byte': (0, -128, 127), + 'nonNegativeInteger': (0, 0, None), + 'non-negative-integer': (0, 0, None), + 'positiveInteger': (0, 1, None), + 'positive-integer': (0, 1, None), + 'unsignedLong': (1, 0, 18446744073709551615L), + 'unsignedInt': (0, 0, 4294967295L), + 'unsignedShort': (0, 0, 65535), + 'unsignedByte': (0, 0, 255), + } + floatlimits = \ + { + 'float': (7.0064923216240861E-46, -3.4028234663852886E+38, + 3.4028234663852886E+38), + 'double': (2.4703282292062327E-324, -1.7976931348623158E+308, + 1.7976931348623157E+308), + } + zerofloatre = '[1-9]' + + def convertType(self, d, t, attrs): + dnn = d or '' + + if t[0] in NS.EXSD_L: + if t[1] == "integer": + try: + d = int(d) + if len(attrs): + d = long(d) + except: + d = long(d) + return d + if self.intlimits.has_key (t[1]): + l = self.intlimits[t[1]] + try: d = int(d) + except: d = long(d) + + if l[1] != None and d < l[1]: + raise UnderflowError, "%s too small" % d + if l[2] != None and d > l[2]: + raise OverflowError, "%s too large" % d + + if l[0] or len(attrs): + return long(d) + return d + if t[1] == "string": + if len(attrs): + return unicode(dnn) + try: + return str(dnn) + except: + return dnn + if t[1] == "boolean": + d = d.strip().lower() + if d in ('0', 'false'): + return 0 + if d in ('1', 'true'): + return 1 + raise AttributeError, "invalid boolean value" + if self.floatlimits.has_key (t[1]): + l = self.floatlimits[t[1]] + s = d.strip().lower() + try: + d = float(s) + except: + # Some platforms don't implement the float stuff. This + # is close, but NaN won't be > "INF" as required by the + # standard. + + if s in ("nan", "inf"): + return 1e300**2 + if s == "-inf": + return -1e300**2 + + raise + + if str (d) == 'nan': + if s != 'nan': + raise ValueError, "invalid %s" % t[1] + elif str (d) == '-inf': + if s != '-inf': + raise UnderflowError, "%s too small" % t[1] + elif str (d) == 'inf': + if s != 'inf': + raise OverflowError, "%s too large" % t[1] + elif d < 0: + if d < l[1]: + raise UnderflowError, "%s too small" % t[1] + elif d > 0: + if d < l[0] or d > l[2]: + raise OverflowError, "%s too large" % t[1] + elif d == 0: + if type(self.zerofloatre) == StringType: + self.zerofloatre = re.compile(self.zerofloatre) + + if self.zerofloatre.search(s): + raise UnderflowError, "invalid %s" % t[1] + + return d + if t[1] in ("dateTime", "date", "timeInstant", "time"): + return self.convertDateTime(d, t[1]) + if t[1] == "decimal": + return float(d) + if t[1] in ("language", "QName", "NOTATION", "NMTOKEN", "Name", + "NCName", "ID", "IDREF", "ENTITY"): + return collapseWhiteSpace(d) + if t[1] in ("IDREFS", "ENTITIES", "NMTOKENS"): + d = collapseWhiteSpace(d) + return d.split() + if t[0] in NS.XSD_L: + if t[1] in ("base64", "base64Binary"): + return base64.decodestring(d) + if t[1] == "hexBinary": + return decodeHexString(d) + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("normalizedString", "token"): + return collapseWhiteSpace(d) + if t[0] == NS.ENC: + if t[1] == "base64": + return base64.decodestring(d) + if t[0] == NS.XSD: + if t[1] == "binary": + try: + e = attrs[(None, 'encoding')] + + if e == 'hex': + return decodeHexString(d) + elif e == 'base64': + return base64.decodestring(d) + except: + pass + + raise Error, "unknown or missing binary encoding" + if t[1] == "uri": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "recurringInstant": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.ENC): + if t[1] == "uriReference": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] == "timePeriod": + return self.convertDateTime(d, t[1]) + if t[1] in ("century", "year"): + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD, NS.XSD2, NS.ENC): + if t[1] == "timeDuration": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD3: + if t[1] == "anyURI": + return urllib.unquote(collapseWhiteSpace(d)) + if t[1] in ("gYearMonth", "gMonthDay"): + return self.convertDateTime(d, t[1]) + if t[1] == "gYear": + return self.convertDateTime(d, t[1]) + if t[1] == "gMonth": + return self.convertDateTime(d, t[1]) + if t[1] == "gDay": + return self.convertDateTime(d, t[1]) + if t[1] == "duration": + return self.convertDateTime(d, t[1]) + if t[0] in (NS.XSD2, NS.XSD3): + if t[1] == "token": + return collapseWhiteSpace(d) + if t[1] == "recurringDate": + return self.convertDateTime(d, t[1]) + if t[1] == "month": + return self.convertDateTime(d, t[1]) + if t[1] == "recurringDay": + return self.convertDateTime(d, t[1]) + if t[0] == NS.XSD2: + if t[1] == "CDATA": + return collapseWhiteSpace(d) + + raise UnknownTypeError, "unknown type `%s'" % (t[0] + ':' + t[1]) + +################################################################################ +# call to SOAPParser that keeps all of the info +################################################################################ +def _parseSOAP(xml_str, rules = None): + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + + parser = xml.sax.make_parser() + t = SOAPParser(rules = rules) + parser.setContentHandler(t) + e = xml.sax.handler.ErrorHandler() + parser.setErrorHandler(e) + + inpsrc = xml.sax.xmlreader.InputSource() + inpsrc.setByteStream(StringIO(xml_str)) + + # turn on namespace mangeling + parser.setFeature(xml.sax.handler.feature_namespaces,1) + + parser.parse(inpsrc) + + return t + +################################################################################ +# SOAPParser's more public interface +################################################################################ +def parseSOAP(xml_str, attrs = 0): + t = _parseSOAP(xml_str) + + if attrs: + return t.body, t.attrs + return t.body + + +def parseSOAPRPC(xml_str, header = 0, body = 0, attrs = 0, rules = None): + t = _parseSOAP(xml_str, rules = rules) + p = t.body._aslist[0] + + # Empty string, for RPC this translates into a void + if type(p) in (type(''), type(u'')) and p in ('', u''): + name = "Response" + for k in t.body.__dict__.keys(): + if k[0] != "_": + name = k + p = structType(name) + + if header or body or attrs: + ret = (p,) + if header : ret += (t.header,) + if body: ret += (t.body,) + if attrs: ret += (t.attrs,) + return ret + else: + return p + + +################################################################################ +# SOAP Builder +################################################################################ +class SOAPBuilder: + _xml_top = '\n' + _xml_enc_top = '\n' + _env_top = '%(ENV_T)s:Envelope %(ENV_T)s:encodingStyle="%(ENC)s"' % \ + NS.__dict__ + _env_bot = '\n' % NS.__dict__ + + # Namespaces potentially defined in the Envelope tag. + + _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T, + NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T, + NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T} + + def __init__(self, args = (), kw = {}, method = None, namespace = None, + header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8', + use_refs = 0, config = Config): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.args = args + self.kw = kw + self.envelope = envelope + self.encoding = encoding + self.method = method + self.namespace = namespace + self.header = header + self.methodattrs= methodattrs + self.use_refs = use_refs + self.config = config + self.out = '' + self.tcounter = 0 + self.ncounter = 1 + self.icounter = 1 + self.envns = {} + self.ids = {} + self.depth = 0 + self.multirefs = [] + self.multis = 0 + self.body = not isinstance(args, bodyType) + + def build(self): + ns_map = {} + + # Cache whether typing is on or not + typed = self.config.typed + + if self.header: + # Create a header. + self.dump(self.header, "Header", typed = typed) + self.header = None # Wipe it out so no one is using it. + if self.body: + # Call genns to record that we've used SOAP-ENV. + self.depth += 1 + body_ns = self.genns(ns_map, NS.ENV)[0] + self.out += "<%sBody>\n" % body_ns + + if self.method: + self.depth += 1 + a = '' + if self.methodattrs: + for (k, v) in self.methodattrs.items(): + a += ' %s="%s"' % (k, v) + + if self.namespace: # Use the namespace info handed to us + methodns, n = self.genns(ns_map, self.namespace) + else: + methodns, n = '', '' + + self.out += '<%s%s%s%s%s>\n' % \ + (methodns, self.method, n, a, self.genroot(ns_map)) + + try: + if type(self.args) != TupleType: + args = (self.args,) + else: + args = self.args + + for i in args: + self.dump(i, typed = typed, ns_map = ns_map) + + for (k, v) in self.kw.items(): + self.dump(v, k, typed = typed, ns_map = ns_map) + except RecursionError: + if self.use_refs == 0: + # restart + b = SOAPBuilder(args = self.args, kw = self.kw, + method = self.method, namespace = self.namespace, + header = self.header, methodattrs = self.methodattrs, + envelope = self.envelope, encoding = self.encoding, + use_refs = 1, config = self.config) + return b.build() + raise + + if self.method: + self.out += "\n" % (methodns, self.method) + self.depth -= 1 + + if self.body: + # dump may add to self.multirefs, but the for loop will keep + # going until it has used all of self.multirefs, even those + # entries added while in the loop. + + self.multis = 1 + + for obj, tag in self.multirefs: + self.dump(obj, tag, typed = typed, ns_map = ns_map) + + self.out += "\n" % body_ns + self.depth -= 1 + + if self.envelope: + e = map (lambda ns: 'xmlns:%s="%s"' % (ns[1], ns[0]), + self.envns.items()) + + self.out = '<' + self._env_top + ' '.join([''] + e) + '>\n' + \ + self.out + \ + self._env_bot + + if self.encoding != None: + self.out = self._xml_enc_top % self.encoding + self.out + + return self.out.encode(self.encoding) + + return self._xml_top + self.out + + def gentag(self): + self.tcounter += 1 + return "v%d" % self.tcounter + + def genns(self, ns_map, nsURI): + if nsURI == None: + return ('', '') + + if type(nsURI) == TupleType: # already a tuple + if len(nsURI) == 2: + ns, nsURI = nsURI + else: + ns, nsURI = None, nsURI[0] + else: + ns = None + + if ns_map.has_key(nsURI): + return (ns_map[nsURI] + ':', '') + + if self._env_ns.has_key(nsURI): + ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI] + return (ns + ':', '') + + if not ns: + ns = "ns%d" % self.ncounter + self.ncounter += 1 + ns_map[nsURI] = ns + if self.config.buildWithNamespacePrefix: + return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI)) + else: + return ('', ' xmlns="%s"' % (nsURI)) + + def genroot(self, ns_map): + if self.depth != 2: + return '' + + ns, n = self.genns(ns_map, NS.ENC) + return ' %sroot="%d"%s' % (ns, not self.multis, n) + + # checkref checks an element to see if it needs to be encoded as a + # multi-reference element or not. If it returns None, the element has + # been handled and the caller can continue with subsequent elements. + # If it returns a string, the string should be included in the opening + # tag of the marshaled element. + + def checkref(self, obj, tag, ns_map): + if self.depth < 2: + return '' + + if not self.ids.has_key(id(obj)): + n = self.ids[id(obj)] = self.icounter + self.icounter = n + 1 + + if self.use_refs == 0: + return '' + + if self.depth == 2: + return ' id="i%d"' % n + + self.multirefs.append((obj, tag)) + else: + if self.use_refs == 0: + raise RecursionError, "Cannot serialize recursive object" + + n = self.ids[id(obj)] + + if self.multis and self.depth == 2: + return ' id="i%d"' % n + + self.out += '<%s href="#i%d"%s/>\n' % (tag, n, self.genroot(ns_map)) + return None + + # dumpers + + def dump(self, obj, tag = None, typed = 1, ns_map = {}): + ns_map = ns_map.copy() + self.depth += 1 + + if type(tag) not in (NoneType, StringType, UnicodeType): + raise KeyError, "tag must be a string or None" + + try: + meth = getattr(self, "dump_" + type(obj).__name__) + meth(obj, tag, typed, ns_map) + except AttributeError: + if type(obj) == LongType: + obj_type = "integer" + else: + obj_type = type(obj).__name__ + + self.out += self.dumper(None, obj_type, obj, tag, typed, + ns_map, self.genroot(ns_map)) + + self.depth -= 1 + + # generic dumper + def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {}, + rootattr = '', id = '', + xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s\n'): + + if nsURI == None: + nsURI = self.config.typesNamespaceURI + + tag = tag or self.gentag() + + a = n = t = '' + if typed and obj_type: + ns, n = self.genns(ns_map, nsURI) + ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n) + + try: a = obj._marshalAttrs(ns_map, self) + except: pass + + try: data = obj._marshalData() + except: data = obj + + return xml % {"tag": tag, "type": t, "data": data, "root": rootattr, + "id": id, "attrs": a} + + def dump_float(self, obj, tag, typed = 1, ns_map = {}): + # Terrible windows hack + if not good_float: + if obj == float(1e300**2): + obj = "INF" + elif obj == float(-1e300**2): + obj = "-INF" + + obj = str(obj) + if obj in ('inf', '-inf'): + obj = str(obj).upper() + elif obj == 'nan': + obj = 'NaN' + self.out += self.dumper(None, "float", obj, tag, typed, ns_map, + self.genroot(ns_map)) + + def dump_string(self, obj, tag, typed = 0, ns_map = {}): + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: data = obj._marshalData() + except: data = obj + + self.out += self.dumper(None, "string", cgi.escape(data), tag, + typed, ns_map, self.genroot(ns_map), id) + + dump_unicode = dump_string + dump_str = dump_string # 4/12/2002 - MAP - for Python 2.2 + + def dump_None(self, obj, tag, typed = 0, ns_map = {}): + tag = tag or self.gentag() + ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0] + + self.out += '<%s %snull="1"%s/>\n' % (tag, ns, self.genroot(ns_map)) + + def dump_list(self, obj, tag, typed = 1, ns_map = {}): + if type(obj) == InstanceType: + data = obj.data + else: + data = obj + + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: + sample = data[0] + empty = 0 + except: + sample = structType() + empty = 1 + + # First scan list to see if all are the same type + same_type = 1 + + if not empty: + for i in data[1:]: + if type(sample) != type(i) or \ + (type(sample) == InstanceType and \ + sample.__class__ != i.__class__): + same_type = 0 + break + + ndecl = '' + if same_type: + if (isinstance(sample, structType)) or \ + type(sample) == DictType: # force to urn struct + + try: + tns = obj._ns or NS.URN + except: + tns = NS.URN + + ns, ndecl = self.genns(ns_map, tns) + + try: + typename = last._typename + except: + typename = "SOAPStruct" + + t = ns + typename + + elif isinstance(sample, anyType): + ns = sample._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + t = ns + sample._type + else: + t = 'ur-type' + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + type(sample).__name__ + else: + t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \ + "ur-type" + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + ens, edecl = self.genns(ns_map, NS.ENC) + ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI) + + self.out += \ + '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %\ + (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl, + self.genroot(ns_map), id, a) + + typed = not same_type + + try: elemsname = obj._elemsname + except: elemsname = "item" + + for i in data: + self.dump(i, elemsname, typed, ns_map) + + self.out += '\n' % tag + + dump_tuple = dump_list + + def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}): + tag = tag or self.gentag() + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + self.out += '<%s%s%s%s>\n' % \ + (tag, id, a, self.genroot(ns_map)) + + for (k, v) in obj.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out += '\n' % tag + dump_dict = dump_dictionary # 4/18/2002 - MAP - for Python 2.2 + + def dump_instance(self, obj, tag, typed = 1, ns_map = {}): + if not tag: + # If it has a name use it. + if isinstance(obj, anyType) and obj._name: + tag = obj._name + else: + tag = self.gentag() + + if isinstance(obj, arrayType): # Array + self.dump_list(obj, tag, typed, ns_map) + return + + if isinstance(obj, faultType): # Fault + cns, cdecl = self.genns(ns_map, NS.ENC) + vns, vdecl = self.genns(ns_map, NS.ENV) + self.out += '''<%sFault %sroot="1"%s%s> +%s +%s +''' % (vns, cns, vdecl, cdecl, obj.faultcode, obj.faultstring) + if hasattr(obj, "detail"): + self.dump(obj.detail, "detail", typed, ns_map) + self.out += "\n" % vns + return + + r = self.genroot(ns_map) + + try: a = obj._marshalAttrs(ns_map, self) + except: a = '' + + if isinstance(obj, voidType): # void + self.out += "<%s%s%s>\n" % (tag, a, r, tag) + return + + id = self.checkref(obj, tag, ns_map) + if id == None: + return + + if isinstance(obj, structType): + # Check for namespace + ndecl = '' + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ns, ndecl = self.genns(ns_map, ns) + tag = ns + tag + self.out += "<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r) + + # If we have order use it. + order = 1 + + for i in obj._keys(): + if i not in obj._keyord: + order = 0 + break + if order: + for i in range(len(obj._keyord)): + self.dump(obj._aslist[i], obj._keyord[i], 1, ns_map) + else: + # don't have pristine order information, just build it. + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + if isinstance(obj, bodyType): + self.multis = 1 + + for v, k in self.multirefs: + self.dump(v, k, typed = typed, ns_map = ns_map) + + self.out += '\n' % tag + + elif isinstance(obj, anyType): + t = '' + + if typed: + ns = obj._validNamespaceURI(self.config.typesNamespaceURI, + self.config.strictNamespaces) + if ns: + ons, ondecl = self.genns(ns_map, ns) + ins, indecl = self.genns(ns_map, + self.config.schemaNamespaceURI) + t = ' %stype="%s%s"%s%s' % \ + (ins, ons, obj._type, ondecl, indecl) + + self.out += '<%s%s%s%s%s>%s\n' % \ + (tag, t, id, a, r, obj._marshalData(), tag) + + else: # Some Class + self.out += '<%s%s%s>\n' % (tag, id, r) + + for (k, v) in obj.__dict__.items(): + if k[0] != "_": + self.dump(v, k, 1, ns_map) + + self.out += '\n' % tag + + +################################################################################ +# SOAPBuilder's more public interface +################################################################################ +def buildSOAP(args=(), kw={}, method=None, namespace=None, header=None, + methodattrs=None,envelope=1,encoding='UTF-8',config=Config): + t = SOAPBuilder(args=args,kw=kw, method=method, namespace=namespace, + header=header, methodattrs=methodattrs,envelope=envelope, + encoding=encoding, config=config) + return t.build() + +################################################################################ +# RPC +################################################################################ + +def SOAPUserAgent(): + return "SOAP.py " + __version__ + " (actzero.com)" + +################################################################################ +# Client +################################################################################ +class SOAPAddress: + def __init__(self, url, config = Config): + proto, uri = urllib.splittype(url) + + # apply some defaults + if uri[0:2] != '//': + if proto != None: + uri = proto + ':' + uri + + uri = '//' + uri + proto = 'http' + + host, path = urllib.splithost(uri) + + try: + int(host) + host = 'localhost:' + host + except: + pass + + if not path: + path = '/' + + if proto not in ('http', 'https'): + raise IOError, "unsupported SOAP protocol" + if proto == 'https' and not config.SSLclient: + raise AttributeError, \ + "SSL client not supported by this Python installation" + + self.proto = proto + self.host = host + self.path = path + + def __str__(self): + return "%(proto)s://%(host)s%(path)s" % self.__dict__ + + __repr__ = __str__ + + +class HTTPTransport: + # Need a Timeout someday? + def call(self, addr, data, soapaction = '', encoding = None, + http_proxy = None, config = Config): + + import httplib + + if not isinstance(addr, SOAPAddress): + addr = SOAPAddress(addr, config) + + # Build a request + if http_proxy: + real_addr = http_proxy + real_path = addr.proto + "://" + addr.host + addr.path + else: + real_addr = addr.host + real_path = addr.path + + if addr.proto == 'https': + r = httplib.HTTPS(real_addr) + else: + r = httplib.HTTP(real_addr) + + r.putrequest("POST", real_path) + + r.putheader("Host", addr.host) + r.putheader("User-agent", SOAPUserAgent()) + t = 'text/xml'; + if encoding != None: + t += '; charset="%s"' % encoding + r.putheader("Content-type", t) + r.putheader("Content-length", str(len(data))) + r.putheader("SOAPAction", '"%s"' % soapaction) + + if config.dumpHeadersOut: + s = 'Outgoing HTTP headers' + debugHeader(s) + print "POST %s %s" % (real_path, r._http_vsn_str) + print "Host:", addr.host + print "User-agent: SOAP.py " + __version__ + " (actzero.com)" + print "Content-type:", t + print "Content-length:", len(data) + print 'SOAPAction: "%s"' % soapaction + debugFooter(s) + + r.endheaders() + + if config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + # send the payload + r.send(data) + + # read response line + code, msg, headers = r.getreply() + + if config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + if headers.headers: + print "HTTP/1.? %d %s" % (code, msg) + print "\n".join(map (lambda x: x.strip(), headers.headers)) + else: + print "HTTP/0.9 %d %s" % (code, msg) + debugFooter(s) + + if config.dumpSOAPIn: + data = r.getfile().read() + + s = 'Incoming SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + if code not in (200, 500): + raise HTTPError(code, msg) + + if not config.dumpSOAPIn: + data = r.getfile().read() + + # return response payload + return data + +################################################################################ +# SOAP Proxy +################################################################################ +class SOAPProxy: + def __init__(self, proxy, namespace = None, soapaction = '', + header = None, methodattrs = None, transport = HTTPTransport, + encoding = 'UTF-8', throw_faults = 1, unwrap_results = 1, + http_proxy=None, config = Config): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + self.proxy = SOAPAddress(proxy, config) + self.namespace = namespace + self.soapaction = soapaction + self.header = header + self.methodattrs = methodattrs + self.transport = transport() + self.encoding = encoding + self.throw_faults = throw_faults + self.unwrap_results = unwrap_results + self.http_proxy = http_proxy + self.config = config + + + def __call(self, name, args, kw, ns = None, sa = None, hd = None, + ma = None): + + ns = ns or self.namespace + ma = ma or self.methodattrs + + if sa: # Get soapaction + if type(sa) == TupleType: sa = sa[0] + else: + sa = self.soapaction + + if hd: # Get header + if type(hd) == TupleType: + hd = hd[0] + else: + hd = self.header + + hd = hd or self.header + + if ma: # Get methodattrs + if type(ma) == TupleType: ma = ma[0] + else: + ma = self.methodattrs + ma = ma or self.methodattrs + + m = buildSOAP(args = args, kw = kw, method = name, namespace = ns, + header = hd, methodattrs = ma, encoding = self.encoding, + config = self.config) + #print m + + r = self.transport.call(self.proxy, m, sa, encoding = self.encoding, + http_proxy = self.http_proxy, + config = self.config) + + #print r + p, attrs = parseSOAPRPC(r, attrs = 1) + + try: + throw_struct = self.throw_faults and \ + isinstance (p, faultType) + except: + throw_struct = 0 + + if throw_struct: + raise p + + # Bubble a regular result up, if there is only element in the + # struct, assume that is the result and return it. + # Otherwise it will return the struct with all the elements + # as attributes. + if self.unwrap_results: + try: + count = 0 + for i in p.__dict__.keys(): + if i[0] != "_": # don't move the private stuff + count += 1 + t = getattr(p, i) + if count == 1: p = t # Only one piece of data, bubble it up + except: + pass + + if self.config.returnAllAttrs: + return p, attrs + return p + + def _callWithBody(self, body): + return self.__call(None, body, {}) + + def __getattr__(self, name): # hook to catch method calls + return self.__Method(self.__call, name, config = self.config) + + # To handle attribute wierdness + class __Method: + # Some magic to bind a SOAP method to an RPC server. + # Supports "nested" methods (e.g. examples.getStateName) -- concept + # borrowed from xmlrpc/soaplib -- www.pythonware.com + # Altered (improved?) to let you inline namespaces on a per call + # basis ala SOAP::LITE -- www.soaplite.com + + def __init__(self, call, name, ns = None, sa = None, hd = None, + ma = None, config = Config): + + self.__call = call + self.__name = name + self.__ns = ns + self.__sa = sa + self.__hd = hd + self.__ma = ma + self.__config = config + if self.__name[0] == "_": + if self.__name in ["__repr__","__str__"]: + self.__call__ = self.__repr__ + else: + self.__call__ = self.__f_call + else: + self.__call__ = self.__r_call + + def __getattr__(self, name): + if self.__name[0] == "_": + # Don't nest method if it is a directive + return self.__class__(self.__call, name, self.__ns, + self.__sa, self.__hd, self.__ma) + + return self.__class__(self.__call, "%s.%s" % (self.__name, name), + self.__ns, self.__sa, self.__hd, self.__ma) + + def __f_call(self, *args, **kw): + if self.__name == "_ns": self.__ns = args + elif self.__name == "_sa": self.__sa = args + elif self.__name == "_hd": self.__hd = args + elif self.__name == "_ma": self.__ma = args + return self + + def __r_call(self, *args, **kw): + return self.__call(self.__name, args, kw, self.__ns, self.__sa, + self.__hd, self.__ma) + + def __repr__(self): + return "<%s at %d>" % (self.__class__, id(self)) + +################################################################################ +# Server +################################################################################ + +# Method Signature class for adding extra info to registered funcs, right now +# used just to indicate it should be called with keywords, instead of ordered +# params. +class MethodSig: + def __init__(self, func, keywords=0, context=0): + self.func = func + self.keywords = keywords + self.context = context + self.__name__ = func.__name__ + + def __call__(self, *args, **kw): + return apply(self.func,args,kw) + +class SOAPContext: + def __init__(self, header, body, attrs, xmldata, connection, httpheaders, + soapaction): + + self.header = header + self.body = body + self.attrs = attrs + self.xmldata = xmldata + self.connection = connection + self.httpheaders= httpheaders + self.soapaction = soapaction + +# A class to describe how header messages are handled +class HeaderHandler: + # Initially fail out if there are any problems. + def __init__(self, header, attrs): + for i in header.__dict__.keys(): + if i[0] == "_": + continue + + d = getattr(header, i) + + try: + fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')]) + except: + fault = 0 + + if fault: + raise faultType, ("%s:MustUnderstand" % NS.ENV_T, + "Don't understand `%s' header element but " + "mustUnderstand attribute is set." % i) + + +################################################################################ +# SOAP Server +################################################################################ +class SOAPServer(SocketServer.TCPServer): + import BaseHTTPServer + + class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def version_string(self): + return '' + \ + 'SOAP.py ' + __version__ + ' (Python ' + \ + sys.version.split()[0] + ')' + + def date_time_string(self): + self.__last_date_time_string = \ + SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ + date_time_string(self) + + return self.__last_date_time_string + + def do_POST(self): + try: + if self.server.config.dumpHeadersIn: + s = 'Incoming HTTP headers' + debugHeader(s) + print self.raw_requestline.strip() + print "\n".join(map (lambda x: x.strip(), + self.headers.headers)) + debugFooter(s) + + data = self.rfile.read(int(self.headers["content-length"])) + + if self.server.config.dumpSOAPIn: + s = 'Incoming SOAP' + debugHeader(s) + print data, + if data[-1] != '\n': + print + debugFooter(s) + + (r, header, body, attrs) = \ + parseSOAPRPC(data, header = 1, body = 1, attrs = 1) + + method = r._name + args = r._aslist + kw = r._asdict + + ns = r._ns + resp = "" + # For fault messages + if ns: + nsmethod = "%s:%s" % (ns, method) + else: + nsmethod = method + + try: + # First look for registered functions + if self.server.funcmap.has_key(ns) and \ + self.server.funcmap[ns].has_key(method): + f = self.server.funcmap[ns][method] + else: # Now look at registered objects + # Check for nested attributes. This works even if + # there are none, because the split will return + # [method] + f = self.server.objmap[ns] + l = method.split(".") + for i in l: + f = getattr(f, i) + except: + resp = buildSOAP(faultType("%s:Client" % NS.ENV_T, + "No method %s found" % nsmethod, + "%s %s" % tuple(sys.exc_info()[0:2])), + encoding = self.server.encoding, + config = self.server.config) + status = 500 + else: + try: + if header: + x = HeaderHandler(header, attrs) + + # If it's wrapped, some special action may be needed + + if isinstance(f, MethodSig): + c = None + + if f.context: # Build context object + c = SOAPContext(header, body, attrs, data, + self.connection, self.headers, + self.headers["soapaction"]) + + if f.keywords: + # This is lame, but have to de-unicode + # keywords + + strkw = {} + + for (k, v) in kw.items(): + strkw[str(k)] = v + if c: + strkw["_SOAPContext"] = c + fr = apply(f, (), strkw) + elif c: + fr = apply(f, args, {'_SOAPContext':c}) + else: + fr = apply(f, args, {}) + else: + fr = apply(f, args, {}) + + if type(fr) == type(self) and \ + isinstance(fr, voidType): + resp = buildSOAP(kw = {'%sResponse' % method: fr}, + encoding = self.server.encoding, + config = self.server.config) + else: + resp = buildSOAP(kw = + {'%sResponse' % method: {'Result': fr}}, + encoding = self.server.encoding, + config = self.server.config) + except Exception, e: + import traceback + info = sys.exc_info() + + if self.server.config.dumpFaultInfo: + s = 'Method %s exception' % nsmethod + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if isinstance(e, faultType): + f = e + else: + f = faultType("%s:Server" % NS.ENV_T, + "Method %s failed." % nsmethod) + + if self.server.config.returnFaultInfo: + f._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(f, 'detail'): + f._setDetail("%s %s" % (info[0], info[1])) + + resp = buildSOAP(f, encoding = self.server.encoding, + config = self.server.config) + status = 500 + else: + status = 200 + except faultType, e: + import traceback + info = sys.exc_info() + + if self.server.config.dumpFaultInfo: + s = 'Received fault exception' + debugHeader(s) + traceback.print_exception(info[0], info[1], + info[2]) + debugFooter(s) + + if self.server.config.returnFaultInfo: + e._setDetail("".join(traceback.format_exception( + info[0], info[1], info[2]))) + elif not hasattr(e, 'detail'): + e._setDetail("%s %s" % (info[0], info[1])) + + resp = buildSOAP(e, encoding = self.server.encoding, + config = self.server.config) + status = 500 + except: + # internal error, report as HTTP server error + if self.server.config.dumpFaultInfo: + import traceback + s = 'Internal exception' + debugHeader(s) + traceback.print_exc () + debugFooter(s) + self.send_response(500) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, 500, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + debugFooter(s) + else: + # got a valid SOAP response + self.send_response(status) + + t = 'text/xml'; + if self.server.encoding != None: + t += '; charset="%s"' % self.server.encoding + self.send_header("Content-type", t) + self.send_header("Content-length", str(len(resp))) + self.end_headers() + + if self.server.config.dumpHeadersOut and \ + self.request_version != 'HTTP/0.9': + s = 'Outgoing HTTP headers' + debugHeader(s) + if self.responses.has_key(status): + s = ' ' + self.responses[status][0] + else: + s = '' + print "%s %d%s" % (self.protocol_version, status, s) + print "Server:", self.version_string() + print "Date:", self.__last_date_time_string + print "Content-type:", t + print "Content-length:", len(resp) + debugFooter(s) + + if self.server.config.dumpSOAPOut: + s = 'Outgoing SOAP' + debugHeader(s) + print resp, + if resp[-1] != '\n': + print + debugFooter(s) + + self.wfile.write(resp) + self.wfile.flush() + + # We should be able to shut down both a regular and an SSL + # connection, but under Python 2.1, calling shutdown on an + # SSL connections drops the output, so this work-around. + # This should be investigated more someday. + + if self.server.config.SSLserver and \ + isinstance(self.connection, SSL.Connection): + self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN | + SSL.SSL_RECEIVED_SHUTDOWN) + else: + self.connection.shutdown(1) + + def log_message(self, format, *args): + if self.server.log: + SOAPServer.BaseHTTPServer.BaseHTTPRequestHandler.\ + log_message (self, format, *args) + + def __init__(self, addr = ('localhost', 8000), + RequestHandler = SOAPRequestHandler, log = 1, encoding = 'UTF-8', + config = Config, namespace = None, ssl_context = None): + + # Test the encoding, raising an exception if it's not known + if encoding != None: + ''.encode(encoding) + + if ssl_context != None and not config.SSLserver: + raise AttributeError, \ + "SSL server not supported by this Python installation" + + self.namespace = namespace + self.objmap = {} + self.funcmap = {} + self.ssl_context = ssl_context + self.encoding = encoding + self.config = config + self.log = log + + self.allow_reuse_address= 1 + + SocketServer.TCPServer.__init__(self, addr, RequestHandler) + + def get_request(self): + sock, addr = SocketServer.TCPServer.get_request(self) + + if self.ssl_context: + sock = SSL.Connection(self.ssl_context, sock) + sock._setup_ssl(addr) + if sock.accept_ssl() != 1: + raise socket.error, "Couldn't accept SSL connection" + + return sock, addr + + def registerObject(self, object, namespace = ''): + if namespace == '': namespace = self.namespace + self.objmap[namespace] = object + + def registerFunction(self, function, namespace = '', funcName = None): + if not funcName : funcName = function.__name__ + if namespace == '': namespace = self.namespace + if self.funcmap.has_key(namespace): + self.funcmap[namespace][funcName] = function + else: + self.funcmap[namespace] = {funcName : function} + + def registerKWObject(self, object, namespace = ''): + if namespace == '': namespace = self.namespace + for i in dir(object.__class__): + if i[0] != "_" and callable(getattr(object, i)): + self.registerKWFunction(getattr(object,i), namespace) + + # convenience - wraps your func for you. + def registerKWFunction(self, function, namespace = '', funcName = None): + self.registerFunction(MethodSig(function,keywords=1), namespace, + funcName) diff --git a/shell/google/__init__.py b/shell/google/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/shell/google/__init__.py diff --git a/shell/google/google.py b/shell/google/google.py new file mode 100755 index 0000000..a20ba51 --- /dev/null +++ b/shell/google/google.py @@ -0,0 +1,638 @@ +""" +Python wrapper for Google web APIs + +This module allows you to access Google's web APIs through SOAP, +to do things like search Google and get the results programmatically. +Described U{here } + +You need a Google-provided license key to use these services. +Follow the link above to get one. These functions will look in +several places (in this order) for the license key: + + - the "license_key" argument of each function + - the module-level LICENSE_KEY variable (call setLicense once to set it) + - an environment variable called GOOGLE_LICENSE_KEY + - a file called ".googlekey" in the current directory + - a file called "googlekey.txt" in the current directory + - a file called ".googlekey" in your home directory + - a file called "googlekey.txt" in your home directory + - a file called ".googlekey" in the same directory as google.py + - a file called "googlekey.txt" in the same directory as google.py + +Sample usage:: + + >>> import google + >>> google.setLicense('...') # must get your own key! + >>> data = google.doGoogleSearch('python') + >>> data.meta.searchTime + 0.043221000000000002 + + >>> data.results[0].URL + 'http://www.python.org/' + + >>> data.results[0].title + 'Python Language Website' + +@newfield contrib: Contributors +@author: Mark Pilgrim +@author: Brian Landers +@license: Python +@version: 0.6 +@contrib: David Ascher, for the install script +@contrib: Erik Max Francis, for the command line interface +@contrib: Michael Twomey, for HTTP proxy support +@contrib: Mark Recht, for patches to support SOAPpy +""" + +__author__ = "Mark Pilgrim (f8dy@diveintomark.org)" +__version__ = "0.6" +__cvsversion__ = "$Revision: 1.5 $"[11:-2] +__date__ = "$Date: 2004/02/25 23:46:07 $"[7:-2] +__copyright__ = "Copyright (c) 2002 Mark Pilgrim" +__license__ = "Python" +__credits__ = """David Ascher, for the install script +Erik Max Francis, for the command line interface +Michael Twomey, for HTTP proxy support""" + +import os, sys, getopt +import GoogleSOAPFacade + +LICENSE_KEY = None +HTTP_PROXY = None + +# +# Constants +# +_url = 'http://api.google.com/search/beta2' +_namespace = 'urn:GoogleSearch' +_googlefile1 = ".googlekey" +_googlefile2 = "googlekey.txt" + +_false = GoogleSOAPFacade.false +_true = GoogleSOAPFacade.true + +_licenseLocations = ( + ( lambda key: key, + 'passed to the function in license_key variable' ), + ( lambda key: LICENSE_KEY, + 'module-level LICENSE_KEY variable (call setLicense to set it)' ), + ( lambda key: os.environ.get( 'GOOGLE_LICENSE_KEY', None ), + 'an environment variable called GOOGLE_LICENSE_KEY' ), + ( lambda key: _contentsOf( os.getcwd(), _googlefile1 ), + '%s in the current directory' % _googlefile1), + ( lambda key: _contentsOf( os.getcwd(), _googlefile2 ), + '%s in the current directory' % _googlefile2), + ( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile1 ), + '%s in your home directory' % _googlefile1), + ( lambda key: _contentsOf( os.environ.get( 'HOME', '' ), _googlefile2 ), + '%s in your home directory' % _googlefile2 ), + ( lambda key: _contentsOf( _getScriptDir(), _googlefile1 ), + '%s in the google.py directory' % _googlefile1 ), + ( lambda key: _contentsOf( _getScriptDir(), _googlefile2 ), + '%s in the google.py directory' % _googlefile2 ) +) + +## ---------------------------------------------------------------------- +## Exceptions +## ---------------------------------------------------------------------- + +class NoLicenseKey(Exception): + """ + Thrown when the API is unable to find a valid license key. + """ + pass + +## ---------------------------------------------------------------------- +## administrative functions (non-API) +## ---------------------------------------------------------------------- + +def _version(): + """ + Display a formatted version string for the module + """ + print """PyGoogle %(__version__)s +%(__copyright__)s +released %(__date__)s + +Thanks to: +%(__credits__)s""" % globals() + + +def _usage(): + """ + Display usage information for the command-line interface + """ + program = os.path.basename(sys.argv[0]) + print """Usage: %(program)s [options] [querytype] query + +options: + -k, --key= Google license key (see important note below) + -1, -l, --lucky show only first hit + -m, --meta show meta information + -r, --reverse show results in reverse order + -x, --proxy= use HTTP proxy + -h, --help print this help + -v, --version print version and copyright information + -t, --test run test queries + +querytype: + -s, --search= search (default) + -c, --cache= retrieve cached page + -p, --spelling= check spelling + +IMPORTANT NOTE: all Google functions require a valid license key; +visit http://www.google.com/apis/ to get one. %(program)s will look in +these places (in order) and use the first license key it finds: + * the key specified on the command line""" % vars() + for get, location in _licenseLocations[2:]: + print " *", location + +## ---------------------------------------------------------------------- +## utility functions (API) +## ---------------------------------------------------------------------- + +def setLicense(license_key): + """ + Set the U{Google APIs } license key + + @param license_key: The new key to use + @type license_key: String + @todo: validate the key? + """ + global LICENSE_KEY + LICENSE_KEY = license_key + + +def getLicense(license_key = None): + """ + Get the U{Google APIs } license key + + The key can be read from any number of locations. See the module-leve + documentation for the search order. + + @return: the license key + @rtype: String + @raise NoLicenseKey: if no valid key could be found + """ + for get, location in _licenseLocations: + rc = get(license_key) + if rc: return rc + _usage() + raise NoLicenseKey, 'get a license key at http://www.google.com/apis/' + + +def setProxy(http_proxy): + """ + Set the HTTP proxy to be used when accessing Google + + @param http_proxy: the proxy to use + @type http_proxy: String + @todo: validiate the input? + """ + global HTTP_PROXY + HTTP_PROXY = http_proxy + + +def getProxy(http_proxy = None): + """ + Get the HTTP proxy we use for accessing Google + + @return: the proxy + @rtype: String + """ + return http_proxy or HTTP_PROXY + + +def _contentsOf(dirname, filename): + filename = os.path.join(dirname, filename) + if not os.path.exists(filename): return None + fsock = open(filename) + contents = fsock.read() + fsock.close() + return contents + + +def _getScriptDir(): + if __name__ == '__main__': + return os.path.abspath(os.path.dirname(sys.argv[0])) + else: + return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__)) + + +def _marshalBoolean(value): + if value: + return _true + else: + return _false + + +def _getRemoteServer( http_proxy ): + return GoogleSOAPFacade.getProxy( _url, _namespace, http_proxy ) + + +## ---------------------------------------------------------------------- +## search results classes +## ---------------------------------------------------------------------- + +class _SearchBase: + def __init__(self, params): + for k, v in params.items(): + if isinstance(v, GoogleSOAPFacade.structType): + v = GoogleSOAPFacade.toDict( v ) + + try: + if isinstance(v[0], GoogleSOAPFacade.structType): + v = [ SOAPProxy.toDict( node ) for node in v ] + + except: + pass + self.__dict__[str(k)] = v + +## ---------------------------------------------------------------------- + +class SearchResultsMetaData(_SearchBase): + """ + Container class for metadata about a given search query's results. + + @ivar documentFiltering: is duplicate page filtering active? + + @ivar searchComments: human-readable informational message + + example:: + + "'the' is a very common word and was not included in your search" + + @ivar estimatedTotalResultsCount: estimated total number of results + for this query. + + @ivar estimateIsExact: is estimatedTotalResultsCount an exact value? + + @ivar searchQuery: search string that initiated this search + + @ivar startIndex: index of the first result returned (zero-based) + + @ivar endIndex: index of the last result returned (zero-based) + + @ivar searchTips: human-readable informational message on how to better + use Google. + + @ivar directoryCategories: list of categories for the search results + + This field is a list of dictionaries, like so:: + + { 'fullViewableName': 'the Open Directory category', + 'specialEncoding': 'encoding scheme of this directory category' + } + + @ivar searchTime: total search time, in seconds + """ + pass + +## ---------------------------------------------------------------------- + +class SearchResult(_SearchBase): + """ + Encapsulates the results from a search. + + @ivar URL: URL + + @ivar title: title (HTML) + + @ivar snippet: snippet showing query context (HTML + + @ivar cachedSize: size of cached version of this result, (KB) + + @ivar relatedInformationPresent: is the "related:" keyword supported? + + Flag indicates that the "related:" keyword is supported for this URL + + @ivar hostName: used when filtering occurs + + When filtering occurs, a maximum of two results from any given + host is returned. When this occurs, the second resultElement + that comes from that host contains the host name in this parameter. + + @ivar directoryCategory: Open Directory category information + + This field is a dictionary with the following values:: + + { 'fullViewableName': 'the Open Directory category', + 'specialEncoding' : 'encoding scheme of this directory category' + } + + @ivar directoryTitle: Open Directory title of this result (or blank) + + @ivar summary: Open Directory summary for this result (or blank) + """ + pass + +## ---------------------------------------------------------------------- + +class SearchReturnValue: + """ + complete search results for a single query + + @ivar meta: L{SearchResultsMetaData} instance for this query + + @ivar results: list of L{SearchResult} objects for this query + """ + def __init__( self, metadata, results ): + self.meta = metadata + self.results = results + +## ---------------------------------------------------------------------- +## main functions +## ---------------------------------------------------------------------- + +def doGoogleSearch( q, start = 0, maxResults = 10, filter = 1, + restrict='', safeSearch = 0, language = '', + inputencoding = '', outputencoding = '',\ + license_key = None, http_proxy = None ): + """ + Search Google using the SOAP API and return the results. + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + See U{http://www.google.com/help/features.html} + for examples of advanced features. Anything that works at the + Google web site will work as a query string in this method. + + You can use the C{start} and C{maxResults} parameters to page + through multiple pages of results. Note that 'maxResults' is + currently limited by Google to 10. + + See the API reference for more advanced examples and a full list of + country codes and topics for use in the C{restrict} parameter, along + with legal values for the C{language}, C{inputencoding}, and + C{outputencoding} parameters. + + You can download the API documentation + U{http://www.google.com/apis/download.html }. + + @param q: search string. + @type q: String + + @param start: (optional) zero-based index of first desired result. + @type start: int + + @param maxResults: (optional) maximum number of results to return. + @type maxResults: int + + @param filter: (optional) flag to request filtering of similar results + @type filter: int + + @param restrict: (optional) restrict results by country or topic. + @type restrict: String + + @param safeSearch: (optional) + @type safeSearch: int + + @param language: (optional) + @type language: String + + @param inputencoding: (optional) + @type inputencoding: String + + @param outputencoding: (optional) + @type outputencoding: String + + @param license_key: (optional) the Google API license key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy to use for talking to Google + @type http_proxy: String + + @return: the search results encapsulated in an object + @rtype: L{SearchReturnValue} + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy ) + remoteserver = _getRemoteServer( http_proxy ) + + filter = _marshalBoolean( filter ) + safeSearch = _marshalBoolean( safeSearch ) + + data = remoteserver.doGoogleSearch( license_key, q, start, maxResults, + filter, restrict, safeSearch, + language, inputencoding, + outputencoding ) + + metadata = GoogleSOAPFacade.toDict( data ) + del metadata["resultElements"] + + metadata = SearchResultsMetaData( metadata ) + + results = [ SearchResult( GoogleSOAPFacade.toDict( node ) ) \ + for node in data.resultElements ] + + return SearchReturnValue( metadata, results ) + +## ---------------------------------------------------------------------- + +def doGetCachedPage( url, license_key = None, http_proxy = None ): + """ + Retrieve a page from the Google cache. + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + @param url: full URL to the page to retrieve + @type url: String + + @param license_key: (optional) the Google API key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy server to use + @type http_proxy: String + + @return: full text of the cached page + @rtype: String + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy ) + remoteserver = _getRemoteServer( http_proxy ) + + return remoteserver.doGetCachedPage( license_key, url ) + +## ---------------------------------------------------------------------- + +def doSpellingSuggestion( phrase, license_key = None, http_proxy = None ): + """ + Get spelling suggestions from Google + + You need a license key to call this function; see the + U{Google APIs } site to get one. + Then you can either pass it to this function every time, or + set it globally; see the L{google} module-level docs for details. + + @param phrase: word or phrase to spell-check + @type phrase: String + + @param license_key: (optional) the Google API key to use + @type license_key: String + + @param http_proxy: (optional) the HTTP proxy to use + @type http_proxy: String + + @return: text of any suggested replacement, or None + """ + license_key = getLicense( license_key ) + http_proxy = getProxy( http_proxy) + remoteserver = _getRemoteServer( http_proxy ) + + return remoteserver.doSpellingSuggestion( license_key, phrase ) + +## ---------------------------------------------------------------------- +## functional test suite (see googletest.py for unit test suite) +## ---------------------------------------------------------------------- + +def _test(): + """ + Run functional test suite. + """ + try: + getLicense(None) + except NoLicenseKey: + return + + print "Searching for Python at google.com..." + data = doGoogleSearch( "Python" ) + _output( data, { "func": "doGoogleSearch"} ) + + print "\nSearching for 5 _French_ pages about Python, " + print "encoded in ISO-8859-1..." + + data = doGoogleSearch( "Python", language = 'lang_fr', + outputencoding = 'ISO-8859-1', + maxResults = 5 ) + + _output( data, { "func": "doGoogleSearch" } ) + + phrase = "Pyhton programming languager" + print "\nTesting spelling suggestions for '%s'..." % phrase + + data = doSpellingSuggestion( phrase ) + + _output( data, { "func": "doSpellingSuggestion" } ) + +## ---------------------------------------------------------------------- +## Command-line interface +## ---------------------------------------------------------------------- + +class _OutputFormatter: + def boil(self, data): + if type(data) == type(u""): + return data.encode("ISO-8859-1", "replace") + else: + return data + +class _TextOutputFormatter(_OutputFormatter): + def common(self, data, params): + if params.get("showMeta", 0): + meta = data.meta + for category in meta.directoryCategories: + print "directoryCategory: %s" % \ + self.boil(category["fullViewableName"]) + for attr in [node for node in dir(meta) if \ + node <> "directoryCategories" and node[:2] <> '__']: + print "%s:" % attr, self.boil(getattr(meta, attr)) + + def doGoogleSearch(self, data, params): + results = data.results + if params.get("feelingLucky", 0): + results = results[:1] + if params.get("reverseOrder", 0): + results.reverse() + for result in results: + for attr in dir(result): + if attr == "directoryCategory": + print "directoryCategory:", \ + self.boil(result.directoryCategory["fullViewableName"]) + elif attr[:2] <> '__': + print "%s:" % attr, self.boil(getattr(result, attr)) + print + self.common(data, params) + + def doGetCachedPage(self, data, params): + print data + self.common(data, params) + + doSpellingSuggestion = doGetCachedPage + +def _makeFormatter(outputFormat): + classname = "_%sOutputFormatter" % outputFormat.capitalize() + return globals()[classname]() + +def _output(results, params): + formatter = _makeFormatter(params.get("outputFormat", "text")) + outputmethod = getattr(formatter, params["func"]) + outputmethod(results, params) + +def main(argv): + """ + Command-line interface. + """ + if not argv: + _usage() + return + q = None + func = None + http_proxy = None + license_key = None + feelingLucky = 0 + showMeta = 0 + reverseOrder = 0 + runTest = 0 + outputFormat = "text" + try: + opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1", + ["search=", "cache=", "spelling=", "key=", "lucky", "meta", + "reverse", "proxy=", "help", "version", "test"]) + except getopt.GetoptError: + _usage() + sys.exit(2) + for opt, arg in opts: + if opt in ("-s", "--search"): + q = arg + func = "doGoogleSearch" + elif opt in ("-c", "--cache"): + q = arg + func = "doGetCachedPage" + elif opt in ("-p", "--spelling"): + q = arg + func = "doSpellingSuggestion" + elif opt in ("-k", "--key"): + license_key = arg + elif opt in ("-l", "-1", "--lucky"): + feelingLucky = 1 + elif opt in ("-m", "--meta"): + showMeta = 1 + elif opt in ("-r", "--reverse"): + reverseOrder = 1 + elif opt in ("-x", "--proxy"): + http_proxy = arg + elif opt in ("-h", "--help"): + _usage() + elif opt in ("-v", "--version"): + _version() + elif opt in ("-t", "--test"): + runTest = 1 + if runTest: + setLicense(license_key) + setProxy(http_proxy) + _test() + if args and not q: + q = args[0] + func = "doGoogleSearch" + if func: + results = globals()[func]( q, http_proxy=http_proxy, + license_key=license_key ) + _output(results, locals()) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/shell/session/Makefile.am b/shell/session/Makefile.am new file mode 100644 index 0000000..a8ba913 --- /dev/null +++ b/shell/session/Makefile.am @@ -0,0 +1,5 @@ +sugardir = $(pythondir)/sugar/session +sugar_PYTHON = \ + __init__.py \ + session.py \ + LogWriter.py diff --git a/shell/session/__init__.py b/shell/session/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/shell/session/__init__.py diff --git a/shell/session/session.py b/shell/session/session.py new file mode 100644 index 0000000..2ddc554 --- /dev/null +++ b/shell/session/session.py @@ -0,0 +1,59 @@ +import os +import signal +from ConfigParser import ConfigParser + +import pygtk +pygtk.require('2.0') +import gtk + +from shell import Shell +from sugar import env + +class Session: + def __init__(self): + self._activity_processes = {} + + def start(self): + shell = Shell() + shell.connect('close', self._shell_close_cb) + shell.start() + + activities = [] + activities_dirs = [] + + for data_dir in env.get_data_dirs(): + act_dir = os.path.join(data_dir, env.get_activities_dir()) + activities_dirs.append(act_dir) + + activities_dirs.append(os.path.join(env.get_user_dir(), 'activities')) + + for activities_dir in activities_dirs: + if os.path.isdir(activities_dir): + for filename in os.listdir(activities_dir): + if filename.endswith(".activity"): + path = os.path.join(activities_dir, filename) + cp = ConfigParser() + cp.read([path]) + python_class = cp.get('Activity', "python_class") + activities.append(python_class) + + for activity in activities: + args = [ 'python', '-m', activity ] + pid = os.spawnvp(os.P_NOWAIT, 'python', args) + self._activity_processes[activity] = pid + + try: + gtk.main() + except KeyboardInterrupt: + print 'Ctrl+C pressed, exiting...' + self.shutdown() + + def _shell_close_cb(self, shell): + self.shutdown() + + def shutdown(self): + # FIXME Obviously we want to notify the activities to + # shutt down rather then killing them down forcefully. + for name in self._activity_processes.keys(): + print 'Shutting down %s' % (name) + os.kill(self._activity_processes[name], signal.SIGTERM) diff --git a/shell/shell.py b/shell/shell.py new file mode 100755 index 0000000..18eceeb --- /dev/null +++ b/shell/shell.py @@ -0,0 +1,516 @@ +import dbus +import dbus.service +import dbus.glib + +import pygtk +pygtk.require('2.0') +import gtk +import pango +import gobject + +import sugar.util +from sugar.chat.ChatWindow import ChatWindow +from sugar.chat.GroupChat import GroupChat +from sugar.session.LogWriter import LogWriter + +from Owner import ShellOwner +from StartPage import StartPage +from WindowManager import WindowManager +from PresenceWindow import PresenceWindow + +class ActivityHost(dbus.service.Object): + + def __init__(self, activity_container, activity_name, default_type, activity_id = None): + self.activity_name = activity_name + self.ellipsize_tab = False + + self.activity_container = activity_container + + if activity_id is None: + self.activity_id = sugar.util.unique_id() + else: + self.activity_id = activity_id + self._default_type = default_type + + self.dbus_object_name = "/com/redhat/Sugar/Shell/Activities/%s" % self.activity_id + + dbus.service.Object.__init__(self, activity_container.service, self.dbus_object_name) + self.socket = gtk.Socket() + self.socket.set_data("sugar-activity", self) + self.socket.show() + + hbox = gtk.HBox(False, 4); + + self.tab_activity_image = gtk.Image() + self.tab_activity_image.set_from_stock(gtk.STOCK_CONVERT, gtk.ICON_SIZE_MENU) + hbox.pack_start(self.tab_activity_image) + #self.tab_activity_image.show() + + self.label_hbox = gtk.HBox(False, 4); + self.label_hbox.connect("style-set", self.__tab_label_style_set_cb) + hbox.pack_start(self.label_hbox) + + self.tab_label = gtk.Label(self.activity_name) + self.tab_label.set_single_line_mode(True) + self.tab_label.set_alignment(0.0, 0.5) + self.tab_label.set_padding(0, 0) + self.tab_label.show() + + close_image = gtk.Image() + close_image.set_from_stock (gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) + close_image.show() + + self.tab_close_button = gtk.Button() + rcstyle = gtk.RcStyle(); + rcstyle.xthickness = rcstyle.ythickness = 0; + self.tab_close_button.modify_style (rcstyle); + self.tab_close_button.add(close_image) + self.tab_close_button.set_relief(gtk.RELIEF_NONE) + self.tab_close_button.set_focus_on_click(False) + self.tab_close_button.connect("clicked", self.tab_close_button_clicked) + + self.label_hbox.pack_start(self.tab_label) + self.label_hbox.pack_start(self.tab_close_button, False, False, 0) + self.label_hbox.show() + + hbox.show() + + self._create_chat() + + notebook = self.activity_container.notebook + index = notebook.append_page(self.socket, hbox) + notebook.set_current_page(index) + + def _create_chat(self): + self._group_chat = GroupChat(self) + + def get_chat(self): + return self._group_chat + + def get_default_type(self): + return self._default_type + + def __close_button_clicked_reply_cb(self): + pass + + def __close_button_clicked_error_cb(self, error): + pass + + def publish(self): + self._group_chat.publish() + self.peer_service.publish() + + def tab_close_button_clicked(self, button): + self.peer_service.close_from_user(reply_handler = self.__close_button_clicked_reply_cb, \ + error_handler = self.__close_button_clicked_error_cb) + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="", \ + out_signature="t") + def get_host_xembed_id(self): + window_id = self.socket.get_id() + #print "window_id = %d"%window_id + return window_id + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="ss", \ + out_signature="") + def set_peer_service_name(self, peer_service_name, peer_object_name): + #print "peer_service_name = %s, peer_object_name = %s"%(peer_service_name, peer_object_name) + self.__peer_service_name = peer_service_name + self.__peer_object_name = peer_object_name + self.peer_service = dbus.Interface(self.activity_container.bus.get_object( \ + self.__peer_service_name, self.__peer_object_name), \ + "com.redhat.Sugar.Activity") + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="b", \ + out_signature="") + def set_ellipsize_tab(self, ellipsize): + self.ellipsize_tab = True + self.update_tab_size() + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="b", \ + out_signature="") + def set_can_close(self, can_close): + if can_close: + self.tab_close_button.show() + else: + self.tab_close_button.hide() + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="b", \ + out_signature="") + def set_tab_show_icon(self, show_icon): + if show_icon: + self.tab_activity_image.show() + else: + self.tab_activity_image.hide() + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="b", \ + out_signature="") + def set_has_changes(self, has_changes): + if has_changes: + attrs = pango.AttrList() + attrs.insert(pango.AttrForeground(50000, 0, 0, 0, -1)) + attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) + self.tab_label.set_attributes(attrs) + else: + self.tab_label.set_attributes(pango.AttrList()) + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="s", \ + out_signature="") + def set_tab_text(self, text): + self.tab_label.set_text(text) + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="ayibiiii", \ + out_signature="") + def set_tab_icon(self, data, colorspace, has_alpha, bits_per_sample, width, height, rowstride): + #print "width=%d, height=%d"%(width, height) + #print " data = ", data + pixstr = "" + for c in data: + # Work around for a bug in dbus < 0.61 where integers + # are not correctly marshalled + if c < 0: + c += 256 + pixstr += chr(c) + + pixbuf = gtk.gdk.pixbuf_new_from_data(pixstr, colorspace, has_alpha, bits_per_sample, width, height, rowstride) + #print pixbuf + self.tab_activity_image.set_from_pixbuf(pixbuf) + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \ + in_signature="", \ + out_signature="") + def shutdown(self): + #print "shutdown" + for owner, activity in self.activity_container.activities[:]: + if activity == self: + self.activity_container.activities.remove((owner, activity)) + + for i in range(self.activity_container.notebook.get_n_pages()): + child = self.activity_container.notebook.get_nth_page(i) + if child == self.socket: + #print "found child" + self.activity_container.notebook.remove_page(i) + break + + del self + + def get_host_activity_id(self): + """Real function that the shell should use for getting the + activity's ID.""" + return self.activity_id + + def get_id(self): + """Interface-type function to match activity.Activity's + get_id() function.""" + return self.activity_id + + def get_object_path(self): + return self.dbus_object_name + + def update_tab_size(self): + if self.ellipsize_tab: + self.tab_label.set_ellipsize(pango.ELLIPSIZE_END) + + context = self.label_hbox.get_pango_context() + font_desc = self.label_hbox.style.font_desc + metrics = context.get_metrics(font_desc, context.get_language()) + char_width = metrics.get_approximate_digit_width() + [w, h] = self.__get_close_icon_size() + tab_width = 15 * pango.PIXELS(char_width) + 2 * w + self.label_hbox.set_size_request(tab_width, -1); + else: + self.tab_label.set_ellipsize(pango.ELLIPSIZE_NONE) + self.label_hbox.set_size_request(-1, -1) + + def __get_close_icon_size(self): + settings = self.label_hbox.get_settings() + return gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU) + + def __tab_label_style_set_cb(self, widget, previous_style): + [w, h] = self.__get_close_icon_size() + self.tab_close_button.set_size_request (w + 5, h + 2) + self.update_tab_size() + +class ActivityContainerSignalHelper(gobject.GObject): + """A gobject whose sole purpose is to distribute signals for + an ActivityContainer object.""" + + __gsignals__ = { + 'local-activity-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + 'local-activity-ended': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) + } + + def __init__(self, parent): + gobject.GObject.__init__(self) + self._parent = parent + + def activity_started(self, activity_id): + self.emit('local-activity-started', self._parent, activity_id) + + def activity_ended(self, activity_id): + self.emit('local-activity-ended', self._parent, activity_id) + +class ActivityContainer(dbus.service.Object): + + def __init__(self, service, bus): + self.activities = [] + + self.bus = bus + self.service = service + + self._signal_helper = ActivityContainerSignalHelper(self) + + dbus.service.Object.__init__(self, self.service, "/com/redhat/Sugar/Shell/ActivityContainer") + bus.add_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged") + + self.window = gtk.Window() + self.window.connect("key-press-event", self.__key_press_event_cb) + self.window.set_title("OLPC Sugar") + + self._fullscreen = False + + self.notebook = gtk.Notebook() + + tab_label = gtk.Label("Everyone") + self._start_page = StartPage(self._signal_helper) + self.notebook.append_page(self._start_page, tab_label) + self._start_page.show() + + self.notebook.show() + self.notebook.connect("switch-page", self.notebook_tab_changed) + self.window.add(self.notebook) + + self.window.connect("destroy", lambda w: gtk.main_quit()) + + self.current_activity = None + + # Create our owner service + self._owner = ShellOwner() + + self._presence_window = PresenceWindow(self) + self._presence_window.set_transient_for(self.window) + self._presence_window.set_decorated(False) + self._presence_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK) + self._presence_window.set_skip_taskbar_hint(True) + + wm = WindowManager(self._presence_window) + + wm.set_width(0.15, WindowManager.SCREEN_RELATIVE) + wm.set_height(1.0, WindowManager.SCREEN_RELATIVE) + wm.set_position(WindowManager.LEFT) + wm.manage() + + self._chat_window = ChatWindow() + self._chat_window.set_transient_for(self.window) + self._chat_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK) + self._chat_window.set_decorated(False) + self._chat_window.set_skip_taskbar_hint(True) + + self._chat_wm = WindowManager(self._chat_window) + + self._chat_wm.set_width(0.5, WindowManager.SCREEN_RELATIVE) + self._chat_wm.set_height(0.5, WindowManager.SCREEN_RELATIVE) + self._chat_wm.set_position(WindowManager.TOP) + self._chat_wm.manage() + + def show(self): + self.window.show() + + def __focus_reply_cb(self): + pass + + def __focus_error_cb(self, error): + pass + + def set_current_activity(self, activity): + self.current_activity = activity + self._presence_window.set_activity(activity) + + if activity: + host_chat = activity.get_chat() + self._chat_window.set_chat(host_chat) + + # For some reason the substitution screw up window position + self._chat_wm.update() + + def notebook_tab_changed(self, notebook, page, page_number): + new_activity = notebook.get_nth_page(page_number).get_data("sugar-activity") + + if self.current_activity != None: + if self.has_activity(self.current_activity): + self.current_activity.peer_service.lost_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb) + + #if self.has_activity(new_activity): + self.set_current_activity(new_activity) + + if self.current_activity != None: + if self.has_activity(self.current_activity): + self.current_activity.peer_service.got_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb) + + + def has_activity(self, activity_to_check_for): + for owner, activity in self.activities[:]: + if activity_to_check_for == activity: + return True + return False + + + def name_owner_changed(self, service_name, old_service_name, new_service_name): + #print "in name_owner_changed: svc=%s oldsvc=%s newsvc=%s"%(service_name, old_service_name, new_service_name) + for owner, activity in self.activities[:]: + if owner == old_service_name: + activity_id = activity.get_host_activity_id() + self._signal_helper.activity_ended(activity_id) + self.activities.remove((owner, activity)) + #self.__print_activities() + + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \ + in_signature="ss", \ + out_signature="s", \ + sender_keyword="sender") + def add_activity(self, activity_name, default_type, sender): + #print "hello world, activity_name = '%s', sender = '%s'"%(activity_name, sender) + activity = ActivityHost(self, activity_name, default_type) + self.activities.append((sender, activity)) + + activity_id = activity.get_host_activity_id() + self._signal_helper.activity_started(activity_id) + + self.current_activity = activity + return activity_id + + @dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \ + in_signature="sss", \ + sender_keyword="sender") + def add_activity_with_id(self, activity_name, default_type, activity_id, sender): + activity = ActivityHost(self, activity_name, default_type, activity_id) + self.activities.append((sender, activity)) + activity_id = activity.get_host_activity_id() + self._signal_helper.activity_started(activity_id) + self.current_activity = activity + + def __print_activities(self): + print "__print_activities: %d activities registered" % len(self.activities) + i = 0 + for owner, activity in self.activities: + print " %d: owner=%s activity_object_name=%s" % (i, owner, activity.dbus_object_name) + i += 1 + + def __key_press_event_cb(self, window, event): + if event.keyval == gtk.keysyms.F11: + if self._fullscreen: + window.unfullscreen() + self._fullscreen = False + else: + window.fullscreen() + self._fullscreen = True + +class ConsoleLogger(dbus.service.Object): + def __init__(self): + session_bus = dbus.SessionBus() + bus_name = dbus.service.BusName('com.redhat.Sugar.Logger', bus=session_bus) + object_path = '/com/redhat/Sugar/Logger' + dbus.service.Object.__init__(self, bus_name, object_path) + + self._window = gtk.Window() + self._window.set_title("Console") + self._window.set_default_size(640, 480) + self._window.connect("delete_event", lambda w, e: w.hide_on_delete()) + + self._nb = gtk.Notebook() + self._window.add(self._nb) + self._nb.show() + + self._consoles = {} + + def set_parent_window(self, window): + window.connect("key-press-event", self.__key_press_event_cb) + self._window.connect("key-press-event", self.__key_press_event_cb) + + def __key_press_event_cb(self, window, event): + if event.keyval == gtk.keysyms.d and \ + event.state & gtk.gdk.CONTROL_MASK: + if self._window.get_property('visible'): + self._window.hide() + else: + self._window.show() + + def _create_console(self, application): + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, + gtk.POLICY_AUTOMATIC) + + console = gtk.TextView() + console.set_wrap_mode(gtk.WRAP_WORD) + + sw.add(console) + console.show() + + self._nb.append_page(sw, gtk.Label(application)) + sw.show() + + return console + + @dbus.service.method('com.redhat.Sugar.Logger') + def log(self, application, message): + if self._consoles.has_key(application): + console = self._consoles[application] + else: + console = self._create_console(application) + self._consoles[application] = console + + buf = console.get_buffer() + buf.insert(buf.get_end_iter(), message) + +class Shell(gobject.GObject): + __gsignals__ = { + 'close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), + } + + def __init__(self): + gobject.GObject.__init__(self) + + def start(self): + console = ConsoleLogger() + + log_writer = LogWriter("Shell", False) + log_writer.start() + + session_bus = dbus.SessionBus() + service = dbus.service.BusName("com.redhat.Sugar.Shell", bus=session_bus) + + activity_container = ActivityContainer(service, session_bus) + activity_container.window.connect('destroy', self.__activity_container_destroy_cb) + activity_container.show() + + wm = WindowManager(activity_container.window) + wm.set_width(640, WindowManager.ABSOLUTE) + wm.set_height(480, WindowManager.ABSOLUTE) + wm.set_position(WindowManager.CENTER) + wm.show() + wm.manage() + + console.set_parent_window(activity_container.window) + + def __activity_container_destroy_cb(self, activity_container): + self.emit('close') + +if __name__ == "__main__": + shell = Shell() + shell.start() + try: + gtk.main() + except KeyboardInterrupt: + print 'Ctrl+c pressed, exiting...' diff --git a/shell/sugar b/shell/sugar new file mode 100755 index 0000000..ccfdd00 --- /dev/null +++ b/shell/sugar @@ -0,0 +1,81 @@ +#!/usr/bin/python + +import sys +import os +import pwd +import random + +import pygtk +pygtk.require('2.0') +import gobject + +def add_to_python_path(path): + sys.path.insert(0, path) + if os.environ.has_key('PYTHONPATH'): + os.environ['PYTHONPATH'] += ':' + path + else: + os.environ['PYTHONPATH'] = path + +def start_dbus(): + curdir = os.path.dirname(__file__) + args = "/bin/dbus-daemon --session --print-address".split() + (dbus_pid, ign1, dbus_stdout, ign3) = gobject.spawn_async(args, flags=gobject.SPAWN_STDERR_TO_DEV_NULL, standard_output=True) + dbus_file = os.fdopen(dbus_stdout) + addr = dbus_file.readline() + addr = addr.strip() + print "dbus-daemon pid is %d, session bus address is %s" % (dbus_pid, addr) + dbus_file.close() + + os.environ["DBUS_SESSION_BUS_ADDRESS"] = addr + + return dbus_pid + +def stop_dbus(dbus_pid): + try: + print 'Closing dbus-daemon, pid %d' % (dbus_pid) + os.kill(dbus_pid, 9) + except OSError: + pass + +i = 0 +dbus_daemon_pid = None +for arg in sys.argv: + if arg == '--test-user': + user = sys.argv[i + 1] + user_dir = os.path.expanduser('~/.sugar-' + user) + os.environ['SUGAR_NICK_NAME'] = user + os.environ['SUGAR_USER_DIR'] = user_dir + dbus_daemon_pid = start_dbus() + started_dbus = True + i += 1 + +if not os.environ.has_key("SUGAR_NICK_NAME"): + nick = pwd.getpwuid(os.getuid())[0] + if not nick or not len(nick): + nick = "Guest %d" % random.randint(1, 10000) + os.environ['SUGAR_NICK_NAME'] = nick + os.environ['SUGAR_USER_DIR'] = os.path.expanduser('~/.sugar') + + +curdir = os.path.abspath(os.path.dirname(__file__)) +basedir = os.path.dirname(curdir) + +if os.path.isfile(os.path.join(basedir, 'sugar/__uninstalled__.py')): + print 'Running sugar from ' + basedir + ' ...' + add_to_python_path(basedir) + add_to_python_path(os.path.join(basedir, 'shell')) + add_to_python_path(os.path.join(basedir, 'activities/browser')) +else: + print 'Running the installed sugar...' + +add_to_python_path(os.path.expanduser('~/.sugar/activities')) + +print 'Redirecting output to the console, press ctrl+d to open it.' + +from session.session import Session + +session = Session() +session.start() + +if dbus_daemon_pid: + stop_dbus(dbus_daemon_pid) -- cgit v0.9.1