Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorerich koslovsky <erikos@localhost.localdomain>2007-08-16 10:33:50 (GMT)
committer erich koslovsky <erikos@localhost.localdomain>2007-08-16 10:33:50 (GMT)
commit749e3e69e4f49249c4d89a157f345b0142d123c6 (patch)
treef8591365eaf27d50f09fb53e2efe09cfbe748da8
parent4ccc35e4b50f9fd410f8fc4ef1b9b5a55231f610 (diff)
Got untracked files. This is no the initial version of the shared browser.
-rw-r--r--browser.dtd17
-rw-r--r--linkbutton.py54
-rw-r--r--linktoolbar.py102
-rw-r--r--messenger.py133
-rw-r--r--tubeconn.py107
-rw-r--r--xmlio.py127
6 files changed, 540 insertions, 0 deletions
diff --git a/browser.dtd b/browser.dtd
new file mode 100644
index 0000000..901b7d9
--- /dev/null
+++ b/browser.dtd
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!ELEMENT browser (#PCDATA|link|session)*>
+<!ATTLIST browser
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT session (#PCDATA)* >
+<!ATTLIST session
+ data CDATA #IMPLIED
+>
+<!ELEMENT link (#PCDATA)* >
+<!ATTLIST link
+ id CDATA #REQUIRED
+ name CDATA #IMPLIED
+ thumb CDATA #IMPLIED
+>
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. <http://www.collabora.co.uk/>
+#
+# 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
+
+
+