From 749e3e69e4f49249c4d89a157f345b0142d123c6 Mon Sep 17 00:00:00 2001 From: erich koslovsky Date: Thu, 16 Aug 2007 10:33:50 +0000 Subject: Got untracked files. This is no the initial version of the shared browser. --- diff --git a/browser.dtd b/browser.dtd new file mode 100644 index 0000000..901b7d9 --- /dev/null +++ b/browser.dtd @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/linkbutton.py b/linkbutton.py new file mode 100644 index 0000000..7f2a203 --- /dev/null +++ b/linkbutton.py @@ -0,0 +1,54 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# 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. + +import gtk + +from sugar.graphics.palette import Palette, WidgetInvoker +from sugar.graphics import style + +class LinkButton(gtk.RadioToolButton): + def __init__(self, link_name, buffer=None, group=None): + gtk.RadioToolButton.__init__(self, group=group) + self._palette = None + self.set_image(buffer) + self.link_name = link_name + self.buf = buffer + + def set_image(self, buffer): + img = gtk.Image() + loader = gtk.gdk.PixbufLoader() + loader.write(buffer) + loader.close() + pixbuf = loader.get_pixbuf() + del loader + + img.set_from_pixbuf(pixbuf) + self.set_icon_widget(img) + img.show() + + def get_palette(self): + return self._palette + + def set_palette(self, palette): + self._palette = palette + self._palette.props.invoker = WidgetInvoker(self.child) + + def set_tooltip(self, text): + self._palette = Palette(text) + self._palette.props.invoker = WidgetInvoker(self.child) + + palette = property(get_palette, set_palette) diff --git a/linktoolbar.py b/linktoolbar.py new file mode 100644 index 0000000..30d3875 --- /dev/null +++ b/linktoolbar.py @@ -0,0 +1,102 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# 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. + +import logging + +import gobject +import gtk + +from gettext import gettext as _ + +from linkbutton import LinkButton +from sugar.graphics.palette import Palette + +_logger = logging.getLogger('linktoolbar') + +class LinkToolbar(gtk.Toolbar): + __gtype_name__ = 'LinkToolbar' + + __gsignals__ = { + 'link-selected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + 'link-rm': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + + def _add_link(self, link_name, buffer, pos): + + if self.get_children(): + group = self.get_children()[0] + else: + group = None + + palette = Palette(link_name) + palette.props.position = Palette.TOP + + link = LinkButton(link_name, buffer, group) + link.set_palette(palette) + link.connect('clicked', self._link_clicked_cb, link_name) + self.insert(link, pos) + link.show() + + menu_item = gtk.MenuItem(_('remove')) + menu_item.connect('activate', self._link_rm_palette_cb, link) + palette.menu.append(menu_item) + menu_item.show() + + #link.props.active = True + + if len(self.get_children()) > 0: + self.show() + + def _link_clicked_cb(self, link, link_name): + if link.get_active(): + _logger.debug('link clicked=%s' %link_name) + self.emit('link-selected', link_name) + + def _rm_link(self): + childs = self.get_children() + for child in childs: + if child.get_active(): + link_name = child.link_name + self.remove(child) + # self.get_children()[0].props.active = True + if len(self.get_children()) is 0: + self.hide() + return link_name + + def _rm_link_messenger(self, linkname): + childs = self.get_children() + for child in childs: + if child.link_name == linkname: + self.remove(child) + if len(self.get_children()) is 0: + self.hide() + return + + def _link_rm_palette_cb(self, widget, link): + self.emit('link-rm', link.link_name) + self.remove(link) + # self.get_children()[0].props.active = True + if len(self.get_children()) is 0: + self.hide() + diff --git a/messenger.py b/messenger.py new file mode 100644 index 0000000..f21dfa5 --- /dev/null +++ b/messenger.py @@ -0,0 +1,133 @@ +# +# Copyright (C) 2007, One Laptop Per Child +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import logging +import os +import dbus +from dbus.gobject_service import ExportedGObject +import base64 + +SERVICE = "org.laptop.WebActivity" +IFACE = SERVICE +PATH = "/org/laptop/WebActivity" + +_logger = logging.getLogger('messenger') + +class Messenger(ExportedGObject): + def __init__(self, tube, is_initiator, linkbar): + ExportedGObject.__init__(self, tube, PATH) + self.tube = tube + self.is_initiator = is_initiator + self.members = [] + self.entered = False + self.linkbar = linkbar + self.tube.watch_participants(self.participant_change_cb) + + def participant_change_cb(self, added, removed): + for handle in removed: + _logger.debug('Member %s left' %(self.tube.participants[handle])) + if self.tube.participants[handle] == self.members[0]: + # TOFIX: the creator leaves the activity, name a new leader + _logger.debug('The creator leaves.') + # if self.id == 1: + # _logger.debug('I become the new leader.') + # self.is_initiator = True + try: + self.members.remove(self.tube.participants[handle]) + except ValueError: + # already absent + pass + + if not self.entered: + self.tube.add_signal_receiver(self._add_link_receiver, '_add_link', IFACE, path=PATH, sender_keyword='sender', + byte_arrays=True) + self.tube.add_signal_receiver(self._rm_link_receiver, '_rm_link', IFACE, path=PATH, sender_keyword='sender', + byte_arrays=True) + self.tube.add_signal_receiver(self._hello_receiver, '_hello_signal', IFACE, path=PATH, sender_keyword='sender') + if self.is_initiator: + _logger.debug('Initialising a new shared browser, I am %s .'%self.tube.get_unique_name()) + self.id = self.tube.get_unique_name() + self.members = [self.id] + else: + self._hello_signal() + self.entered = True + + @dbus.service.signal(IFACE, signature='') + def _hello_signal(self): + '''Notify current members that you joined ''' + _logger.debug('Sending hello to all') + + def _hello_receiver(self, sender=None): + '''A new member joined the activity, sync linkbar''' + self.members.append(sender) + if self.is_initiator: + self.tube.get_object(sender, PATH).init_members(self.members) + for child in self.linkbar.get_children(): + self.tube.get_object(sender, PATH).transfer_links(child.link_name, base64.b64encode(child.buf),dbus_interface=IFACE, reply_handler=self.reply_transfer, error_handler=lambda e:self.error_transfer(e, 'transfering file')) + + def reply_transfer(self): + pass + + def error_transfer(self, e, when): + _logger.error('Error %s: %s'%(when, e)) + + @dbus.service.method(dbus_interface=IFACE, in_signature='as', out_signature='') + def init_members(self, members): + '''Sync the list of members ''' + _logger.debug('Data received to sync member list.') + self.members = members + self.id = self.members.index(self.tube.get_unique_name()) + + @dbus.service.method(dbus_interface=IFACE, in_signature='ss', out_signature='') + def transfer_links(self, linkname, thumb): + '''Sync the link list with the others ''' + _logger.debug('Data received to sync link list.') + self.linkbar._add_link(linkname, base64.b64decode(thumb), -1) + + def add_link(self, linkname, pix): + _logger.debug('Add Link: %s '%linkname) + thumb = base64.b64encode(pix) + self._add_link(linkname, thumb) + + @dbus.service.signal(IFACE, signature='ss') + def _add_link(self, linkname, thumb): + '''Signal to send the link information (add)''' + + def _add_link_receiver(self, linkname, thumb, sender=None): + '''Member sent a link''' + handle = self.tube.bus_name_to_handle[sender] + if self.tube.self_handle != handle: + data = base64.b64decode(thumb) + self.linkbar._add_link(linkname, data, -1) + _logger.debug('Added link: %s to linkbar.'%(linkname)) + + def rm_link(self, linkname): + _logger.debug('Remove Link: %s '%linkname) + self._rm_link(linkname) + + @dbus.service.signal(IFACE, signature='s') + def _rm_link(self, linkname): + '''Signal to send the link information (rm)''' + + def _rm_link_receiver(self, linkname, sender=None): + '''Member sent signal to remove a link''' + handle = self.tube.bus_name_to_handle[sender] + if self.tube.self_handle != handle: + self.linkbar._rm_link_messenger(linkname) + _logger.debug('Removed link: %s from linkbar.'%(linkname)) + diff --git a/tubeconn.py b/tubeconn.py new file mode 100644 index 0000000..d1c1403 --- /dev/null +++ b/tubeconn.py @@ -0,0 +1,107 @@ +# This should eventually land in telepathy-python, so has the same license: + +# Copyright (C) 2007 Collabora Ltd. +# +# This program 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.1 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +__all__ = ('TubeConnection',) +__docformat__ = 'reStructuredText' + + +import logging + +from dbus.connection import Connection + + +logger = logging.getLogger('telepathy.tubeconn') + + +class TubeConnection(Connection): + + def __new__(cls, conn, tubes_iface, tube_id, address=None, + group_iface=None, mainloop=None): + if address is None: + address = tubes_iface.GetDBusServerAddress(tube_id) + self = super(TubeConnection, cls).__new__(cls, address, + mainloop=mainloop) + + self._tubes_iface = tubes_iface + self.tube_id = tube_id + self.participants = {} + self.bus_name_to_handle = {} + self._mapping_watches = [] + + if group_iface is None: + method = conn.GetSelfHandle + else: + method = group_iface.GetSelfHandle + method(reply_handler=self._on_get_self_handle_reply, + error_handler=self._on_get_self_handle_error) + + return self + + def _on_get_self_handle_reply(self, handle): + self.self_handle = handle + match = self._tubes_iface.connect_to_signal('DBusNamesChanged', + self._on_dbus_names_changed) + self._tubes_iface.GetDBusNames(self.tube_id, + reply_handler=self._on_get_dbus_names_reply, + error_handler=self._on_get_dbus_names_error) + self._dbus_names_changed_match = match + + def _on_get_self_handle_error(self, e): + logging.basicConfig() + logger.error('GetSelfHandle failed: %s', e) + + def close(self): + self._dbus_names_changed_match.remove() + self._on_dbus_names_changed(self.tube_id, (), self.participants.keys()) + super(TubeConnection, self).close() + + def _on_get_dbus_names_reply(self, names): + self._on_dbus_names_changed(self.tube_id, names, ()) + + def _on_get_dbus_names_error(self, e): + logging.basicConfig() + logger.error('GetDBusNames failed: %s', e) + + def _on_dbus_names_changed(self, tube_id, added, removed): + if tube_id == self.tube_id: + for handle, bus_name in added: + if handle == self.self_handle: + # I've just joined - set my unique name + self.set_unique_name(bus_name) + self.participants[handle] = bus_name + self.bus_name_to_handle[bus_name] = handle + + # call the callback while the removed people are still in + # participants, so their bus names are available + for callback in self._mapping_watches: + callback(added, removed) + + for handle in removed: + bus_name = self.participants.pop(handle, None) + self.bus_name_to_handle.pop(bus_name, None) + + def watch_participants(self, callback): + self._mapping_watches.append(callback) + if self.participants: + # GetDBusNames already returned: fake a participant add event + # immediately + added = [] + for k, v in self.participants.iteritems(): + added.append((k, v)) + callback(added, []) diff --git a/xmlio.py b/xmlio.py new file mode 100644 index 0000000..fe1b4d8 --- /dev/null +++ b/xmlio.py @@ -0,0 +1,127 @@ +# +# Copyright (C) 2006, 2007, One Laptop Per Child +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import libxml2 +import os +import logging +import base64 + + +_logger = logging.getLogger('xmlio') + + +class Xmlio(object): + ''' The xmlio of the activity. Contains methods to read and write + the configuration for a browser session to and from xml. + ''' + + def __init__(self, dtdpath, linkbar): + self.linkbar = linkbar + self.data = {} + self.dtdpath = dtdpath + self.data['name'] = 'first' + self.session_data = '' + + try: + self.dtd = libxml2.parseDTD(None, os.path.join(self.dtdpath, 'browser.dtd')) + except libxml2.parserError, e: + _logger.error('Init: no browser.dtd found ' +str(e)) + self.dtd = None + self.ctxt = libxml2.newValidCtxt() + + def read(self, filepath): + ''' reads the configuration from an xml file ''' + + try: + doc = libxml2.parseFile(filepath) + if doc.validateDtd(self.ctxt, self.dtd): + + # get the requested nodes + xpa = doc.xpathNewContext() + res = xpa.xpathEval("//*") + + # write their content to the data structure + for elem in res: + attributes = elem.get_properties() + thumb = '' + link_name = '' + id = None + if( elem.name == 'link' ): + for attribute in attributes: + if(attribute.name == 'id'): + id = int(attribute.content) + elif(attribute.name == 'name'): + link_name = attribute.content + elif(attribute.name == 'thumb'): + thumb = attribute.content + self.linkbar._add_link(link_name, base64.b64decode(thumb), id) + + elif( elem.name == 'session' ): + for attribute in attributes: + if(attribute.name == 'data'): + self.session_data = attribute.content + + elif( elem.name == 'browser' ): + for attribute in attributes: + if(attribute.name == 'name'): + self.data['name'] = attribute.content + + xpa.xpathFreeContext() + else: + _logger.error('Read: Error in validation of the file') + doc.freeDoc() + return 1 + doc.freeDoc() + return 0 + except libxml2.parserError, e: + _logger.error('Read: Error parsing file ' +str(e)) + return 2 + + + def write(self, filepath): + ''' writes the configuration to an xml file ''' + doc = libxml2.newDoc("1.0") + root = doc.newChild(None, "browser", None) + + if(self.data.get('name', None) != None): + root.setProp("name", self.data['name']) + else: + _logger.error('Write: No name is specified. Can not write session.') + return 1 + + elem = root.newChild(None, "session", None) + elem.setProp("data", self.session_data) + + for child in self.linkbar.get_children(): + elem = root.newChild(None, "link", None) + elem.setProp("id", str(self.linkbar.get_item_index(child))) + elem.setProp("name", child.link_name) + elem.setProp("thumb", base64.b64encode(child.buf)) + + + if doc.validateDtd(self.ctxt, self.dtd): + doc.saveFormatFile(filepath, 1) + else: + _logger.error('Write: Error in validation of the file') + doc.freeDoc() + return 2 + doc.freeDoc() + return 0 + + + -- cgit v0.9.1