Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/shared.py
diff options
context:
space:
mode:
Diffstat (limited to 'shared.py')
-rwxr-xr-xshared.py387
1 files changed, 387 insertions, 0 deletions
diff --git a/shared.py b/shared.py
new file mode 100755
index 0000000..19da1c8
--- /dev/null
+++ b/shared.py
@@ -0,0 +1,387 @@
+# -*- mode:python; tab-width:4; indent-tabs-mode:nil; -*-
+
+# shared.py
+#
+# Top-level class responsible for making Classroom Presenter a shared activity.
+# Kris Plunkett <kp86@cs.washington.edu>
+#
+# 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 logging
+import os
+import gobject
+
+import telepathy
+import telepathy.client
+
+import dbus
+from dbus.service import method, signal
+from dbus.gobject_service import ExportedGObject
+
+from sugar.presence import presenceservice
+from sugar.presence.tubeconn import TubeConnection
+
+import utils
+from sharedslides import SharedSlides
+
+SERVICE = "edu.washington.cs.ClassroomPresenterXO"
+IFACE = SERVICE
+PATH = "/edu/washington/cs/ClassroomPresenterXO"
+
+
+class Shared(ExportedGObject):
+
+ __gsignals__ = {
+ 'navigation-lock-change' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)),
+ 'deck-download-complete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+ }
+
+ def __init__(self, activity, deck, work_path):
+ gobject.GObject.__init__(self)
+
+ self.__activity = activity
+ self.__deck = deck
+ self.__logger = logging.getLogger('Shared')
+
+ self.__is_initiating = True # defaults to instructor
+ self.__shared_slides = None
+ self.__got_dbus_tube = False
+ self.__locked = False
+ self.__pservice = presenceservice.get_instance()
+ #self.__owner = self.__pservice.get_owner()
+
+ self.__cpxo_path = os.path.join(work_path, 'deck.cpxo')
+
+ self.__activity.connect('shared', self.shared_cb)
+ self.__activity.connect('joined', self.joined_cb)
+
+ def shared_cb(self, activity):
+ """ Called when the activity is shared """
+ self.__logger.debug('The activity has been shared.')
+ self.__is_initiating = True
+ self.__activity.write_file(self.__cpxo_path)
+ self.__deck.set_is_initiating(is_init=True)
+ self.shared_setup()
+
+ def joined_cb(self, activity):
+ """ Called when the activity is joined """
+ self.__logger.debug('Joined another activity.')
+ self.__is_initiating = False
+ self.__deck.set_is_initiating(is_init=False)
+ # for showing slide deck download progress
+ activity.do_progress_view()
+ activity.set_progress(0.0)
+ self.shared_setup()
+
+ def shared_setup(self):
+ """ Called by joined_cb and shared_cb because all of this needs to happen
+ whether we are sharing or joining the activity """
+
+ self.__shared_activity = self.__activity.get_shared_activity()
+
+ if self.__shared_activity is None:
+ self.__logger.error('Failed to share or join activity!')
+ return
+
+ self.__tubes_chan = self.__shared_activity.telepathy_tubes_chan
+ self.__iface = self.__tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+
+ self.__text_chan = self.__shared_activity.telepathy_text_chan
+ self.__iface_grp = self.__text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+
+ self.__conn = self.__shared_activity.telepathy_conn
+ self.__my_handle = self.__conn.GetSelfHandle()
+
+ #self.__shared_activity.connect('buddy-joined', self.buddy_joined_cb)
+ #self.__shared_activity.connect('buddy-left', self.buddy_left_cb)
+
+ # takes care of downloading (and then sharing) the slide deck over stream tubes
+ self.__shared_slides = SharedSlides(self.__is_initiating, self.__cpxo_path,
+ self.__shared_activity, self.__activity.read_file)
+ self.__shared_slides.connect('deck-download-complete', self.deck_download_complete_cb)
+
+ # now for the dbus tube
+ self.__iface.connect_to_signal('NewTube', self.new_tube_cb)
+
+ if (self.__is_initiating):
+ self.__logger.debug("We are sharing, making a dbus tube and setting locked nav mode.")
+ self.lock_nav()
+ id = self.__iface.OfferDBusTube(SERVICE, {})
+ else:
+ self.__logger.debug("We are joining, looking for the global dbus tube.")
+ self.__tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self.list_tubes_reply_cb,
+ error_handler=self.list_tubes_error_cb)
+
+
+ """ --- START DBUS TUBE CODE --- """
+
+ def deck_download_complete_cb(self, object):
+ """ Catches the local deck_download_complete signal and sends the appropriate dbus signal """
+ self.__logger.debug("Deck download is complete, sending Deck_Download_Complete dbus signal.")
+ self.Deck_Download_Complete()
+ self.emit('deck-download-complete')
+
+ def student_dl_complete_cb(self, sender):
+ """ Catches the Deck_Download_Complete dbus signal from students, lets us know that they
+ are ready to have initial state information pushed onto them """
+ self.__logger.debug("Got Deck_Download_Complete dbus signal, pushing initial state info to student.")
+ proxy_object = self.__dbus_tube.get_object(sender, PATH)
+ proxy_object.Push_Initial_State(self.__locked, self.__deck.getIndex(),
+ dbus_interface=IFACE)
+
+ def list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self.new_tube_cb(*tube_info)
+
+ def list_tubes_error_cb(self, e):
+ self.__logger.error('ListTubes() failed: %s', e)
+
+ def new_tube_cb(self, tube_id, initiator, type, service, params, state):
+ self.__logger.debug('New tube: ID=%d initator=%d type=%d service=%s params=%r state=%d',
+ tube_id, initiator, type, service, params, state)
+ if (not self.__got_dbus_tube and type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
+ if( state == telepathy.TUBE_STATE_LOCAL_PENDING):
+ self.__iface.AcceptDBusTube(tube_id)
+
+ self.__dbus_tube = TubeConnection(self.__conn, self.__iface, tube_id,
+ group_iface=self.__iface_grp)
+ self.__got_dbus_tube = True
+ self.__logger.debug("Got our dbus tube!")
+
+ # lots of stuff to do once we get our tube
+ if (self.__is_initiating):
+ self.__deck.connect('slide-changed', self.send_slide_changed_signal)
+ self.__deck.connect('local-ink-added', self.send_ink_path)
+ self.__deck.connect('instructor-ink-cleared', self.instr_clear_ink_cb)
+ self.__deck.connect('instructor-ink-removed', self.instr_remove_ink_cb)
+ self.__deck.connect('ink-broadcast', self.bcast_submission_cb)
+ self.__dbus_tube.add_signal_receiver(self.student_dl_complete_cb, 'Deck_Download_Complete',
+ IFACE, path=PATH, sender_keyword='sender')
+ self.__dbus_tube.add_signal_receiver(self.receive_submission_cb,
+ 'Send_Submission', IFACE, path=PATH)
+ else:
+ self.__deck.connect('ink-submitted', self.submit_ink_cb)
+ self.__dbus_tube.add_signal_receiver(self.slide_changed_cb, 'Slide_Changed',
+ IFACE, path=PATH)
+ self.__dbus_tube.add_signal_receiver(self.lock_nav_cb, 'Lock_Nav',
+ IFACE, path=PATH)
+ self.__dbus_tube.add_signal_receiver(self.add_ink_path_cb, 'Add_Ink_Path',
+ IFACE, path=PATH)
+ self.__dbus_tube.add_signal_receiver(self.recv_instr_clear_ink_cb, 'Instructor_Clear_Ink',
+ IFACE, path=PATH)
+ self.__dbus_tube.add_signal_receiver(self.recv_instr_remove_ink_cb, 'Instructor_Remove_Ink',
+ IFACE, path=PATH)
+ self.__dbus_tube.add_signal_receiver(self.receive_submission_cb,
+ 'Bcast_Submission', IFACE, path=PATH)
+
+ #self.__dbus_tube.watch_participants(self.participant_change_cb)
+
+ super(Shared, self).__init__(self.__dbus_tube, PATH)
+
+ def participant_change_cb(self, added, removed):
+ """ Callback on instructor XO for when someone joins or leaves the tube """
+ for handle, bus_name in added:
+ buddy = self._get_buddy(handle)
+ if buddy is not None:
+ if handle != self.__my_handle and self.__is_initiating:
+ self.__logger.debug("New student joined: %s", buddy.props.nick)
+
+ for handle in removed:
+ buddy = self._get_buddy(handle)
+ if buddy is not None:
+ self.__logger.debug('Buddy %s was removed' % buddy.props.nick)
+
+ @signal(dbus_interface=IFACE, signature='u')
+ def Slide_Changed(self, slide_num):
+ """ Signals joiners to move to given slide """
+ self.__logger.debug("Sending the Slide_Changed signal with slide num %d.", slide_num)
+ pass
+
+ @signal(dbus_interface=IFACE, signature='')
+ def Deck_Download_Complete(self):
+ """ Signal from the student informing instructor that the deck download has finished """
+ self.__logger.debug("Sending Deck_Download_Complete signal, ready for initial state info.")
+ pass
+
+ @signal(dbus_interface=IFACE, signature='u')
+ def Lock_Nav(self, lock):
+ """ Signals joiners to lock or unlock navigation """
+ self.__logger.debug("Sending Lock_Nav signal with bool %u", lock)
+ pass
+
+ @method(dbus_interface=IFACE, in_signature='uu', out_signature='')
+ def Push_Initial_State(self, locked, slide_idx):
+ """ Called on student XO to push initial state info """
+ # push current slide index and go to that slide
+ self.__deck.goToIndex(slide_idx, is_local=False)
+
+ # push nav lock information
+ if locked:
+ self.lock_nav()
+ else:
+ self.unlock_nav()
+
+ def send_slide_changed_signal(self, widget):
+ """ Arbitrates the sending of the Slide_Changed signal """
+ self.__logger.debug("Got the slide-changed signal.")
+ if self.__locked:
+ self.__logger.debug("Navigation is locked, sending Slide_Changed to students.")
+ self.Slide_Changed(self.__deck.getIndex())
+
+ def slide_changed_cb(self, slide_idx):
+ """ Called on the joiners when they receive the Slide_Changed signal """
+ self.__logger.debug("Received the Slide_Changed signal and changing to slide %d.",
+ slide_idx)
+
+ self.__deck.goToIndex(slide_idx, is_local=False)
+
+ def lock_nav_cb(self, lock):
+ """ Called on joiners when they receive the Lock_Nav signal """
+ self.__logger.debug("Received the Lock_Nav signal with bool %u", lock)
+ if (lock):
+ self.lock_nav()
+ else:
+ self.unlock_nav()
+
+ def lock_mode_switch(self, widget=None):
+ """ Switches the lock mode from locked to unlocked and vice versa """
+ # first switch our own lock mode
+ if (self.__locked):
+ self.unlock_nav()
+ else:
+ self.lock_nav()
+
+ # if we are instructor, tell student XOs to go into our new lock mode
+ if (self.__is_initiating):
+ self.Lock_Nav(self.__locked)
+
+ def lock_nav(self):
+ self.__logger.debug("Locking navigation.")
+ self.__locked = True
+ # if we are the instructor, force students to jump to our slide
+ if self.__got_dbus_tube and self.__is_initiating:
+ self.Slide_Changed(self.__deck.getIndex())
+ self.__deck.set_locked_mode(locked=True)
+ self.emit('navigation-lock-change', self.__locked)
+
+ def unlock_nav(self):
+ self.__logger.debug("Unlocking navigation.")
+ self.__locked = False
+ self.__deck.set_locked_mode(locked=False)
+ self.emit('navigation-lock-change', self.__locked)
+
+ def send_ink_path(self, widget, inkstr):
+ self.__logger.debug("send_ink_path called")
+ if (self.__is_initiating and self.__got_dbus_tube):
+ self.Add_Ink_Path(self.__deck.getIndex(), inkstr)
+
+ @signal(dbus_interface=IFACE, signature='us')
+ def Add_Ink_Path(self, slide_idx, pathstr):
+ self.__logger.debug("Sending new ink path")
+ pass
+
+ def _get_buddy(self, cs_handle):
+ """Get a Buddy from a channel specific handle."""
+ self.__logger.debug('Trying to find owner of handle %u...', cs_handle)
+ my_csh = self.__iface_grp.GetSelfHandle()
+ self.__logger.debug('My handle in that group is %u', my_csh)
+ if my_csh == cs_handle:
+ handle = self.__conn.GetSelfHandle()
+ self.__logger.debug('CS handle %u belongs to me, %u', cs_handle, handle)
+ elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ self.__logger.debug('CS handle %u belongs to %u', cs_handle, handle)
+ else:
+ handle = cs_handle
+ self.__logger.debug('non-CS handle %u belongs to itself', handle)
+ # XXX: deal with failure to get the handle owner
+ assert handle != 0
+ return self.__pservice.get_buddy_by_telepathy_handle(
+ self.__conn.service_name, self.__conn.object_path, handle)
+
+ def add_ink_path_cb(self, idx, inkstr):
+ self.__logger.debug("Received new ink path")
+ self.__deck.addInkToSlide(inkstr, islocal=False, n=idx)
+
+ def submit_ink_cb(self, widget, inks, text):
+ if not self.__is_initiating and self.__got_dbus_tube:
+ cur_idx = self.__deck.getIndex()
+ my_csh = self.__iface_grp.GetSelfHandle()
+ buddy = self._get_buddy(my_csh)
+
+ if buddy is not None:
+ sender = buddy.props.nick
+ else:
+ sender = 'Unknown'
+
+ self.__logger.debug("Sending submission: idx '%d', sender '%s'.", cur_idx, sender)
+ self.Send_Submission(sender, cur_idx, inks, text)
+
+ @signal(dbus_interface=IFACE, signature='suss')
+ def Send_Submission(self, sender, slide_idx, inks, text):
+ pass
+
+ def receive_submission_cb(self, sender, slide_idx, inks, text):
+ self.__logger.debug("Received submission from '%s'.", sender)
+ self.__deck.addSubmission(sender, inks, text, slide_idx)
+
+ def bcast_submission_cb(self, widget, whofrom, inks, text):
+ if self.__is_initiating and self.__got_dbus_tube:
+ cur_idx = self.__deck.getIndex()
+ self.Bcast_Submission(whofrom, cur_idx, inks, text)
+
+ @signal(dbus_interface=IFACE, signature='suss')
+ def Bcast_Submission(self, sender, slide_idx, inks, text):
+ pass
+
+ def instr_clear_ink_cb(self, widget, idx):
+ if self.__is_initiating and self.__got_dbus_tube:
+ self.Instructor_Clear_Ink(idx)
+
+ @signal(dbus_interface=IFACE, signature='u')
+ def Instructor_Clear_Ink(self, idx):
+ pass
+
+ def recv_instr_clear_ink_cb(self, idx):
+ self.__deck.clearInstructorInk(idx)
+
+ def instr_remove_ink_cb(self, widget, uid, idx):
+ if self.__is_initiating and self.__got_dbus_tube:
+ self.Instructor_Remove_Ink(uid, idx)
+
+ @signal(dbus_interface=IFACE, signature='uu')
+ def Instructor_Remove_Ink(self, uid, idx):
+ pass
+
+ def recv_instr_remove_ink_cb(self, uid, idx):
+ self.__deck.removeInstructorPathByUID(uid, idx)
+
+
+ """ --- END DBUS TUBE CODE --- """
+
+ # DEPRECATED
+ def buddy_joined_cb(self, activity, buddy):
+ """ Called when a buddy joins the activity """
+ if self.__is_initiating is True:
+ utils.run_dialog("Instructor", buddy.props.nick + " has joined!")
+
+ # DEPRECATED
+ def buddy_left_cb(self, activity, buddy):
+ """ Called when a buddy leaves the activity """
+ if self.__is_initiating is True:
+ utils.run_dialog("Instructor", buddy.props.nick + " has left!")
+
+gobject.type_register(Shared)