diff --git a/services/presence/server_plugin.py b/services/presence/server_plugin.py index daa31aa1..a886fdb8 100644 --- a/services/presence/server_plugin.py +++ b/services/presence/server_plugin.py @@ -146,6 +146,12 @@ class ServerPlugin(gobject.GObject): self._ip4am = psutils.IP4AddressMonitor.get_instance() self._ip4am.connect('address-changed', self._ip4_address_changed_cb) + self._publish_channel = None + self._subscribe_channel = None + self._subscribe_members = set() + self._subscribe_local_pending = set() + self._subscribe_remote_pending = set() + def _ip4_address_changed_cb(self, ip4am, address): _logger.debug("::: IP4 address now %s" % address) if address: diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py new file mode 100644 index 00000000..eecc86a4 --- /dev/null +++ b/sugar/graphics/palette.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python + +# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com) +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +import gobject +import pango + +ALIGNMENT_BOTTOM_LEFT = 0 +ALIGNMENT_BOTTOM_RIGHT = 1 +ALIGNMENT_LEFT_BOTTOM = 2 +ALIGNMENT_LEFT_TOP = 3 +ALIGNMENT_RIGHT_BOTTOM = 4 +ALIGNMENT_RIGHT_TOP = 5 +ALIGNMENT_TOP_LEFT = 6 +ALIGNMENT_TOP_RIGHT = 7 + +class Palette(gtk.Window): + __gtype_name__ = 'SugarPalette' + + __gproperties__ = { + 'parent': (object, None, None, gobject.PARAM_READWRITE), + + 'alignment': (gobject.TYPE_INT, None, None, 0, 7, ALIGNMENT_BOTTOM_LEFT, + gobject.PARAM_READWRITE) + } + + _PADDING = 1 + _WIN_BORDER = 5 + + def __init__(self): + + gobject.GObject.__init__(self) + gtk.Window.__init__(self, gtk.WINDOW_POPUP) + + self._palette_label = gtk.Label() + self._palette_label.set_ellipsize(pango.ELLIPSIZE_START) + + self._separator = gtk.HSeparator() + self._separator.hide() + + self._menu_bar = gtk.MenuBar() + self._menu_bar.set_pack_direction(gtk.PACK_DIRECTION_TTB) + self._menu_bar.show() + + self._content = gtk.HBox() + self._button_bar = gtk.HButtonBox() + + # Set main container + vbox = gtk.VBox(False, 0) + vbox.pack_start(self._palette_label, False, False, self._PADDING) + vbox.pack_start(self._separator, True, True, self._PADDING) + vbox.pack_start(self._menu_bar, True, True, self._PADDING) + vbox.pack_start(self._content, True, True, self._PADDING) + vbox.pack_start(self._button_bar, True, True, self._PADDING) + vbox.show() + + # FIXME + self.connect('focus_out_event', self._close_palette) + + self.set_border_width(self._WIN_BORDER) + self.add(vbox) + self.show() + + # Set the palette position using as reference the + # parent widget + self._set_palette_position() + + def do_set_property(self, pspec, value): + + if pspec.name == 'parent': + self._parent_widget = value + elif pspec.name == 'alignment': + self._alignment = value + else: + raise AssertionError + + def _set_palette_position(self): + + window_axis = self._parent_widget.window.get_origin() + parent_rectangle = self._parent_widget.get_allocation() + + palette_width, palette_height = self.get_size_request() + + # POSITIONING NOT TESTED + if self._alignment == ALIGNMENT_BOTTOM_LEFT: + move_x = window_axis[0] + parent_rectangle.x + move_y = window_axis[1] + parent_rectangle.y + parent_rectangle.height + + elif self._alignment == ALIGNMENT_BOTTOM_RIGHT: + move_x = parent_rectangle.x - palette_width + move_y = window_axis[1] + parent_rectangle.y + parent_rectangle.height + + elif self._alignment == ALIGNMENT_LEFT_BOTTOM: + move_x = parent_rectangle.x - palette_width + move_y = palette_rectangle.y + + elif self._alignment == ALIGNMENT_LEFT_TOP: + move_x = parent_rectangle.x - palette_width + move_y = parent_rectangle.y + palette_rectangle.height + + elif self._alignment == ALIGNMENT_RIGHT_BOTTOM: + move_x = parent_rectangle.x + parent_rectangle.width + move_y = parent_rectangle.y + + elif self._alignment == ALIGNMENT_RIGHT_TOP: + move_x = parent_rectangle.x + parent_rectangle.width + move_y = parent_rectangle.y + parent_rectangle.height + + elif self._alignment == ALIGNMENT_TOP_LEFT: + move_x = parent_rectangle.x + move_y = parent_rectangle.y - palette_height + + elif self._alignment == ALIGNMENT_TOP_RIGHT: + move_x = (parent_rectangle.x + parent_rectangle.width) - palette_width + move_y = parent_rectangle.y - palette_height + + self.move(move_x, move_y) + + def _close_palette(self, widget, event): + self.destroy() + + def set_primary_state(self, label, accel_path=None): + if accel_path != None: + item = gtk.MenuItem(label) + item.set_accel_path(accel_path) + self.append_menu_item(item) + self._separator.hide() + else: + self._palette_label.set_text(label) + self._separator.show() + + def append_menu_item(self, item): + self._menu_bar.append(item) + item.show() + + def set_content(self, widget): + self._content.pack_start(widget, True, True, self._PADDING) + widget.show() + + def append_button(self, button): + self._button_bar.pack_start(button, True, True, self._PADDING) + button.show() + diff --git a/sugar/network.py b/sugar/network.py new file mode 100644 index 00000000..e315cdbf --- /dev/null +++ b/sugar/network.py @@ -0,0 +1,523 @@ +# Copyright (C) 2006, Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# pylint: disable-msg = W0221 + +import socket +import os +import threading +import traceback +import xmlrpclib +import sys +import httplib +import urllib +import fcntl + +import gobject +import SimpleXMLRPCServer +import SimpleHTTPServer +import SocketServer + + +__authinfos = {} + +def _add_authinfo(authinfo): + __authinfos[threading.currentThread()] = authinfo + +def get_authinfo(): + return __authinfos.get(threading.currentThread()) + +def _del_authinfo(): + del __authinfos[threading.currentThread()] + + +class GlibTCPServer(SocketServer.TCPServer): + """GlibTCPServer + + Integrate socket accept into glib mainloop. + """ + + allow_reuse_address = True + request_queue_size = 20 + + def __init__(self, server_address, RequestHandlerClass): + SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass) + self.socket.setblocking(0) # Set nonblocking + + # Watch the listener socket for data + gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept) + + def _handle_accept(self, source, condition): + """Process incoming data on the server's socket by doing an accept() + via handle_request().""" + if not (condition & gobject.IO_IN): + return True + self.handle_request() + return True + + +class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + """RequestHandler class that integrates with Glib mainloop. It writes + the specified file to the client in chunks, returning control to the + mainloop between chunks. + """ + + CHUNK_SIZE = 4096 + + def __init__(self, request, client_address, server): + self._file = None + self._srcid = 0 + SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server) + + def log_request(self, code='-', size='-'): + pass + + def do_GET(self): + """Serve a GET request.""" + self._file = self.send_head() + if self._file: + self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT | gobject.IO_ERR, self._send_next_chunk) + else: + self._file.close() + self._cleanup() + + def _send_next_chunk(self, source, condition): + if condition & gobject.IO_ERR: + self._cleanup() + return False + if not (condition & gobject.IO_OUT): + self._cleanup() + return False + data = self._file.read(self.CHUNK_SIZE) + count = os.write(self.wfile.fileno(), data) + if count != len(data) or len(data) != self.CHUNK_SIZE: + self._cleanup() + return False + return True + + def _cleanup(self): + if self._file: + self._file.close() + if self._srcid > 0: + gobject.source_remove(self._srcid) + self._srcid = 0 + if not self.wfile.closed: + self.wfile.flush() + self.wfile.close() + self.rfile.close() + + def finish(self): + """Close the sockets when we're done, not before""" + pass + + def send_head(self): + """Common code for GET and HEAD commands. + + This sends the response code and MIME headers. + + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + + ** [dcbw] modified to send Content-disposition filename too + """ + path = self.translate_path(self.path) + f = None + if os.path.isdir(path): + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path) + ctype = self.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + self.send_error(404, "File not found") + return None + self.send_response(200) + self.send_header("Content-type", ctype) + self.send_header("Content-Length", str(os.fstat(f.fileno())[6])) + self.send_header("Content-Disposition", 'attachment; filename="%s"' % os.path.basename(path)) + self.end_headers() + return f + +class GlibURLDownloader(gobject.GObject): + """Grabs a URL in chunks, returning to the mainloop after each chunk""" + + __gsignals__ = { + 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + 'error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + CHUNK_SIZE = 4096 + + def __init__(self, url, destdir=None): + self._url = url + if not destdir: + destdir = "/tmp" + self._destdir = destdir + self._srcid = 0 + self._fname = None + self._outf = None + gobject.GObject.__init__(self) + + def start(self): + self._info = urllib.urlopen(self._url) + self._suggested_fname = self._get_filename_from_headers(self._info.headers) + import tempfile + garbage, path = urllib.splittype(self._url) + garbage, path = urllib.splithost(path or "") + path, garbage = urllib.splitquery(path or "") + path, garbage = urllib.splitattr(path or "") + suffix = os.path.splitext(path)[1] + (self._outf, self._fname) = tempfile.mkstemp(suffix=suffix, dir=self._destdir) + + fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY) + self._srcid = gobject.io_add_watch(self._info.fp.fileno(), + gobject.IO_IN | gobject.IO_ERR, + self._read_next_chunk) + + def _get_filename_from_headers(self, headers): + if not headers.has_key("Content-Disposition"): + return None + + ftag = "filename=" + data = headers["Content-Disposition"] + fidx = data.find(ftag) + if fidx < 0: + return None + fname = data[fidx+len(ftag):] + if fname[0] == '"' or fname[0] == "'": + fname = fname[1:] + if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'": + fname = fname[:len(fname)-1] + return fname + + def _read_next_chunk(self, source, condition): + if condition & gobject.IO_ERR: + self.cleanup() + os.remove(self._fname) + self.emit("error", "Error downloading file.") + return False + elif not (condition & gobject.IO_IN): + # shouldn't get here, but... + return True + + try: + data = self._info.fp.read(self.CHUNK_SIZE) + count = os.write(self._outf, data) + if len(data) < self.CHUNK_SIZE: + self.cleanup() + self.emit("finished", self._fname, self._suggested_fname) + return False + if count < len(data): + self.cleanup() + self.emit("error", "Error writing to download file.") + return False + except Exception, err: + self.cleanup() + self.emit("error", "Error downloading file: %s" % err) + return False + return True + + def cleanup(self): + if self._srcid > 0: + gobject.source_remove(self._srcid) + self._srcid = 0 + del self._info + self._info = None + os.close(self._outf) + self._outf = None + + +class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): + """ GlibXMLRPCRequestHandler + + The stock SimpleXMLRPCRequestHandler and server don't allow any way to pass + the client's address and/or SSL certificate into the function that actually + _processes_ the request. So we have to store it in a thread-indexed dict. + """ + + def do_POST(self): + _add_authinfo(self.client_address) + try: + SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self) + except socket.timeout: + pass + except socket.error, e: + print "Error (%s): socket error - '%s'" % (self.client_address, e) + except: + print "Error while processing POST:" + traceback.print_exc() + _del_authinfo() + +class GlibXMLRPCServer(GlibTCPServer, SimpleXMLRPCServer.SimpleXMLRPCDispatcher): + """GlibXMLRPCServer + + Use nonblocking sockets and handle the accept via glib rather than + blocking on accept(). + """ + + def __init__(self, addr, requestHandler=GlibXMLRPCRequestHandler, + logRequests=0, allow_none=False): + self.logRequests = logRequests + if sys.version_info[:3] >= (2, 5, 0): + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding="utf-8") + else: + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) + GlibTCPServer.__init__(self, addr, requestHandler) + + def _marshaled_dispatch(self, data, dispatch_method = None): + """Dispatches an XML-RPC method from marshalled (XML) data. + + XML-RPC methods are dispatched from the marshalled (XML) data + using the _dispatch method and the result is returned as + marshalled data. For backwards compatibility, a dispatch + function can be provided as an argument (see comment in + SimpleXMLRPCRequestHandler.do_POST) but overriding the + existing method through subclassing is the prefered means + of changing method dispatch behavior. + """ + + params, method = xmlrpclib.loads(data) + + # generate response + try: + if dispatch_method is not None: + response = dispatch_method(method, params) + else: + response = self._dispatch(method, params) + # wrap response in a singleton tuple + response = (response,) + response = xmlrpclib.dumps(response, methodresponse=1) + except xmlrpclib.Fault, fault: + response = xmlrpclib.dumps(fault) + except: + print "Exception while processing request:" + traceback.print_exc() + + # report exception back to server + response = xmlrpclib.dumps( + xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)) + ) + + return response + + +class GlibHTTP(httplib.HTTP): + """Subclass HTTP so we can return it's connection class' socket.""" + def connect(self, host=None, port=None): + httplib.HTTP.connect(self, host, port) + self._conn.sock.setblocking(0) + +class GlibXMLRPCTransport(xmlrpclib.Transport): + """Integrate the request with the glib mainloop rather than blocking.""" + ## + # Connect to server. + # + # @param host Target host. + # @return A connection handle. + + def __init__(self, use_datetime=0): + if sys.version_info[:3] >= (2, 5, 0): + xmlrpclib.Transport.__init__(self, use_datetime) + + def make_connection(self, host): + """Use our own connection object so we can get its socket.""" + # create a HTTP connection object from a host descriptor + host, extra_headers, x509 = self.get_host_info(host) + return GlibHTTP(host) + + ## + # Send a complete request, and parse the response. + # + # @param host Target host. + # @param handler Target PRC handler. + # @param request_body XML-RPC request body. + # @param verbose Debugging flag. + # @return Parsed response. + + def start_request(self, host, handler, request_body, verbose=0, reply_handler=None, error_handler=None, user_data=None): + """Do the first half of the request by sending data to the remote + server. The bottom half bits get run when the remote server's response + actually comes back.""" + # issue XML-RPC request + + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + self.send_request(h, handler, request_body) + self.send_host(h, host) + self.send_user_agent(h) + self.send_content(h, request_body) + + # Schedule a GIOWatch so we don't block waiting for the response + gobject.io_add_watch(h._conn.sock, gobject.IO_IN, self._finish_request, + h, host, handler, verbose, reply_handler, error_handler, user_data) + + def _finish_request(self, source, condition, h, host, handler, verbose, reply_handler=None, error_handler=None, user_data=None): + """Parse and return response when the remote server actually returns it.""" + if not (condition & gobject.IO_IN): + return True + + try: + errcode, errmsg, headers = h.getreply() + except socket.error, err: + if err[0] != 104: + raise socket.error(err) + else: + if error_handler: + gobject.idle_add(error_handler, err, user_data) + return False + + if errcode != 200: + raise xmlrpclib.ProtocolError(host + handler, errcode, errmsg, headers) + self.verbose = verbose + response = self._parse_response(h.getfile(), h._conn.sock) + if reply_handler: + # Coerce to a list so we can append user data + response = response[0] + if not isinstance(response, list): + response = [response] + response.append(user_data) + gobject.idle_add(reply_handler, *response) + return False + +class _Method: + """Right, so python people thought it would be funny to make this + class private to xmlrpclib.py...""" + # some magic to bind an XML-RPC method to an RPC server. + # supports "nested" methods (e.g. examples.getStateName) + def __init__(self, send, name): + self.__send = send + self.__name = name + def __getattr__(self, name): + return _Method(self.__send, "%s.%s" % (self.__name, name)) + def __call__(self, *args, **kwargs): + return self.__send(self.__name, *args, **kwargs) + + +class GlibServerProxy(xmlrpclib.ServerProxy): + """Subclass xmlrpclib.ServerProxy so we can run the XML-RPC request + in two parts, integrated with the glib mainloop, such that we don't + block anywhere. + + Using this object is somewhat special; it requires more arguments to each + XML-RPC request call than the normal xmlrpclib.ServerProxy object: + + client = GlibServerProxy("http://127.0.0.1:8888") + user_data = "bar" + xmlrpc_arg1 = "test" + xmlrpc_arg2 = "foo" + client.test(xmlrpc_test_cb, user_data, xmlrpc_arg1, xmlrpc_arg2) + + Here, 'xmlrpc_test_cb' is the callback function, which has the following + signature: + + def xmlrpc_test_cb(result_status, response, user_data=None): + ... + """ + def __init__(self, uri, encoding=None, verbose=0, allow_none=0): + self._transport = GlibXMLRPCTransport() + self._encoding = encoding + self._verbose = verbose + self._allow_none = allow_none + xmlrpclib.ServerProxy.__init__(self, uri, self._transport, encoding, verbose, allow_none) + + # get the url + import urllib + urltype, uri = urllib.splittype(uri) + if urltype not in ("http", "https"): + raise IOError, "unsupported XML-RPC protocol" + self._host, self._handler = urllib.splithost(uri) + if not self._handler: + self._handler = "/RPC2" + + def __request(self, methodname, *args, **kwargs): + """Call the method on the remote server. We just start the request here + and the transport itself takes care of scheduling the response callback + when the remote server returns the response. We don't want to block anywhere.""" + + request = xmlrpclib.dumps(args, methodname, encoding=self._encoding, + allow_none=self._allow_none) + + reply_hdl = kwargs.get("reply_handler") + err_hdl = kwargs.get("error_handler") + udata = kwargs.get("user_data") + try: + response = self._transport.start_request( + self._host, + self._handler, + request, + verbose=self._verbose, + reply_handler=reply_hdl, + error_handler=err_hdl, + user_data=udata + ) + except socket.error, exc: + if err_hdl: + gobject.idle_add(err_hdl, exc, udata) + + def __getattr__(self, name): + # magic method dispatcher + return _Method(self.__request, name) + + +class Test(object): + def test(self, arg1, arg2): + print "Request got %s, %s" % (arg1, arg2) + return "success", "bork" + +def xmlrpc_success_cb(response, resp2, loop): + print "Response was %s %s" % (response, resp2) + loop.quit() + +def xmlrpc_error_cb(err, loop): + print "Error: %s" % err + loop.quit() + +def xmlrpc_test(loop): + client = GlibServerProxy("http://127.0.0.1:8888") + client.test("bar", "baz", + reply_handler=xmlrpc_success_cb, + error_handler=xmlrpc_error_cb, + user_data=loop) + +def main(): + loop = gobject.MainLoop() + server = GlibXMLRPCServer(("", 8888)) + inst = Test() + server.register_instance(inst) + gobject.idle_add(xmlrpc_test, loop) + try: + loop.run() + except KeyboardInterrupt: + print 'Ctrl+C pressed, exiting...' + print "Done." + +if __name__ == "__main__": + main() diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py index a0b60ee1..4e332721 100644 --- a/sugar/presence/presenceservice.py +++ b/sugar/presence/presenceservice.py @@ -312,32 +312,6 @@ class PresenceService(gobject.GObject): return None return self._new_object(buddy_op) - def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path, - handle): - """Retrieve single Buddy object for the given public key - - :Parameters: - `tp_conn_name` : str - The well-known bus name of a Telepathy connection - `tp_conn_path` : dbus.ObjectPath - The object path of the Telepathy connection - `handle` : int or long - The handle of a Telepathy contact on that connection, - of type HANDLE_TYPE_CONTACT. This may not be a - channel-specific handle. - :Returns: the Buddy object, or None if the buddy is not found - """ - try: - buddy_op = self._ps.GetBuddyByTelepathyHandle(tp_conn_name, - tp_conn_path, - handle) - except dbus.exceptions.DBusException, err: - _logger.warn('Unable to retrieve buddy handle for handle %u at ' - 'conn %s:%s from presence service: %s', - handle, tp_conn_name, tp_conn_path, err) - return None - return self._new_object(buddy_op) - def get_owner(self): """Retrieves the laptop "owner" Buddy object.""" try: