From 244ddc77daab80ec433757c90797190f65df2dba Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Tue, 10 Mar 2020 18:01:28 +0530 Subject: [PATCH 1/7] update collabwrapper --- textchannelwrapper.py | 325 +++++++++++++++++++++++++++--------------- 1 file changed, 214 insertions(+), 111 deletions(-) diff --git a/textchannelwrapper.py b/textchannelwrapper.py index a4c3fec..44ccfcf 100644 --- a/textchannelwrapper.py +++ b/textchannelwrapper.py @@ -16,40 +16,51 @@ # Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA ''' -The wrapper module provides an abstraction over the sugar +The wrapper module provides an abstraction over the Sugar collaboration system. + Using CollabWrapper ------------------- -1. Implement the `get_data` and `set_data` methods in your activity - class:: +1. Add `get_data` and `set_data` methods to the activity class:: + def get_data(self): # return plain python objects - things that can be encoded # using the json module return dict( text=self._entry.get_text() ) + def set_data(self, data): # data will be the same object returned by get_data self._entry.set_text(data.get('text')) -2. Make your CollabWrapper instance:: + +2. Make a CollabWrapper instance:: + def __init__(self, handle): sugar3.activity.activity.Activity.__init__(self, handle) self._collab = CollabWrapper(self) self._collab.connect('message', self.__message_cb) - # setup your activity + + # setup your activity here + self._collab.setup() -3. Post any changes to the CollabWrapper. The changes will be sent to - other users if any are connected:: + +3. Post any changes of shared state to the CollabWrapper. The changes + will be sent to other buddies if any are connected, for example:: + def __entry_changed_cb(self, *args): self._collab.post(dict( action='entry_changed', new_text=self._entry.get_text() )) -4. Handle incoming messages:: - def __message_cb(self, collab, buddy, message): + +4. Handle incoming messages, for example:: + + def __message_cb(self, collab, buddy, msg): action = msg.get('action') if action == 'entry_changed': self._entry.set_text(msg.get('new_text')) + ''' import os @@ -57,34 +68,36 @@ def __message_cb(self, collab, buddy, message): import socket from gettext import gettext as _ +import gi +gi.require_version('TelepathyGLib', '0.12') from gi.repository import GObject from gi.repository import Gio from gi.repository import GLib +from gi.repository import TelepathyGLib import dbus - -from telepathy.interfaces import \ - CHANNEL_INTERFACE, \ - CHANNEL_INTERFACE_GROUP, \ - CHANNEL_TYPE_TEXT, \ - CHANNEL_TYPE_FILE_TRANSFER, \ - CONN_INTERFACE_ALIASING, \ - CHANNEL, \ - CLIENT -from telepathy.constants import \ - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, \ - CONNECTION_HANDLE_TYPE_CONTACT, \ - CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, \ - CONNECTION_HANDLE_TYPE_CONTACT, \ - SOCKET_ADDRESS_TYPE_UNIX, \ - SOCKET_ACCESS_CONTROL_LOCALHOST -from telepathy.client import Connection, Channel +from dbus import PROPERTIES_IFACE + +CHANNEL_INTERFACE = TelepathyGLib.IFACE_CHANNEL +CHANNEL_INTERFACE_GROUP = TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP +CHANNEL_TYPE_TEXT = TelepathyGLib.IFACE_CHANNEL_TYPE_TEXT +CHANNEL_TYPE_FILE_TRANSFER = TelepathyGLib.IFACE_CHANNEL_TYPE_FILE_TRANSFER +CONN_INTERFACE_ALIASING = TelepathyGLib.IFACE_CONNECTION_INTERFACE_ALIASING +CONN_INTERFACE = TelepathyGLib.IFACE_CONNECTION +CHANNEL = TelepathyGLib.IFACE_CHANNEL +CLIENT = TelepathyGLib.IFACE_CLIENT +CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES = \ + TelepathyGLib.ChannelGroupFlags.CHANNEL_SPECIFIC_HANDLES +CONNECTION_HANDLE_TYPE_CONTACT = TelepathyGLib.HandleType.CONTACT +CHANNEL_TEXT_MESSAGE_TYPE_NORMAL = TelepathyGLib.ChannelTextMessageType.NORMAL +SOCKET_ADDRESS_TYPE_UNIX = TelepathyGLib.SocketAddressType.UNIX +SOCKET_ACCESS_CONTROL_LOCALHOST = TelepathyGLib.SocketAccessControl.LOCALHOST from sugar3.presence import presenceservice from sugar3.activity.activity import SCOPE_PRIVATE from sugar3.graphics.alert import NotifyAlert import logging -_logger = logging.getLogger('text-channel-wrapper') +_logger = logging.getLogger('CollabWrapper') ACTION_INIT_REQUEST = '!!ACTION_INIT_REQUEST' ACTION_INIT_RESPONSE = '!!ACTION_INIT_RESPONSE' @@ -93,31 +106,52 @@ def __message_cb(self, collab, buddy, message): class CollabWrapper(GObject.GObject): ''' - The collaboration wrapper provides a high level abstraction over the - collaboration system. The wrapper deals with setting up the channels, - encoding and decoding messages, initialization and alerting the user - to the status. - When a user joins the activity, it will query the leader for the - contents. The leader will return the result of the activity's - `get_data` function which will be passed to the `set_data` function - on the new user's computer. - The `message` signal is called when a message is received from a - buddy. It has 2 arguments. The first is the buddy, as a - :class:`sugar3.presence.buddy.Buddy`. The second is the decoded - content of the message, same as that posted by the other instance. - The `joined` signal is emitted when the buddy joins a running - activity. If the user shares and activity, the joined signal - is not emitted. By the time this signal is emitted, the channels - will be setup so all messages will flow through. - The `buddy_joined` and `buddy_left` signals are emitted when - another user joins or leaves the activity. They both a - :class:`sugar3.presence.buddy.Buddy` as their only argument. + The wrapper provides a high level abstraction over the + collaboration system. The wrapper deals with setting up the + channels, encoding and decoding messages, initialization and + alerting the caller to the status. + + An activity instance is initially private, but may be shared. Once + shared, an instance will remain shared for as long as the activity + runs. On stop, the journal will preserve the instance as shared, + and on resume the instance will be shared again. + + When the caller shares an activity instance, they are the leader, + and other buddies may join. The instance is now a shared activity. + + When the caller joins a shared activity, the leader will call + `get_data`, and the caller's `set_data` will be called with the + result. + + The `joined` signal is emitted when the caller joins a shared + activity. One or more `buddy_joined` signals will be emitted before + this signal. The signal is not emitted to the caller who first + shared the activity. There are no arguments. + + The `buddy_joined` signal is emitted when another buddy joins the + shared activity. At least one will be emitted before the `joined` + signal. The caller will never be mentioned, but is assumed to be + part of the set. The signal passes a + :class:`sugar3.presence.buddy.Buddy` as the only argument. + + The `buddy_left` signal is emitted when another user leaves the + shared activity. The signal is not emitted during quit. The signal + passes a :class:`sugar3.presence.buddy.Buddy` as the only argument. + + Any buddy may call `post` to send a message to all buddies. Each + buddy will receive a `message` signal. + + The `message` signal is emitted when a `post` is received from any + buddy. The signal has two arguments. The first is a + :class:`sugar3.presence.buddy.Buddy`. The second is the message. + + Any buddy may call `send_file_memory` or `send_file_file` to + transfer a file to all buddies. A description is to be given. + Each buddy will receive an `incoming_file` signal. + The `incoming_file` signal is emitted when a file transfer is - received from a buddy. The first argument is the object representing - the transfer, as a - :class:`sugar3.presence.filetransfer.IncomingFileTransfer`. The seccond - argument is the description, as passed to the `send_file_*` function - on the sender's client + received. The signal has two arguments. The first is a + :class:`IncomingFileTransfer`. The second is the description. ''' message = GObject.Signal('message', arg_types=[object, object]) @@ -127,24 +161,28 @@ class CollabWrapper(GObject.GObject): incoming_file = GObject.Signal('incoming_file', arg_types=[object, object]) def __init__(self, activity): + _logger.debug('__init__') GObject.GObject.__init__(self) self.activity = activity self.shared_activity = activity.shared_activity self._leader = False self._init_waiting = False self._text_channel = None + self._owner = presenceservice.get_instance().get_owner() def setup(self): ''' - Setup must be called to so that the activity can join or share + Setup must be called so that the activity can join or share if appropriate. + .. note:: As soon as setup is called, any signal, `get_data` or - `set_data` call must be made. This means that your - activity must have set up enough so these functions can - work. For example, place this at the end of the activity's + `set_data` call may occur. This means that the activity + must have set up enough so these functions can work. For + example, call setup at the end of the activity `__init__` function. ''' + _logger.debug('setup') # Some glue to know if we are launching, joining, or resuming # a shared activity. if self.shared_activity: @@ -159,6 +197,7 @@ def setup(self): self._alert(_('Joining activity...'), _('Please wait for the connection...')) else: + self._leader = True if not self.activity.metadata or self.activity.metadata.get( 'share-scope', SCOPE_PRIVATE) == \ SCOPE_PRIVATE: @@ -181,15 +220,15 @@ def _alert(self, title, msg=None): def __shared_cb(self, sender): ''' Callback for when activity is shared. ''' + _logger.debug('__shared_cb') + # FIXME: may be called twice, but we should only act once self.shared_activity = self.activity.shared_activity self._setup_text_channel() self._listen_for_channels() - self._leader = True - _logger.debug('I am sharing...') - def __joined_cb(self, sender): '''Callback for when an activity is joined.''' + _logger.debug('__joined_cb') self.shared_activity = self.activity.shared_activity if not self.shared_activity: return @@ -199,11 +238,14 @@ def __joined_cb(self, sender): self._init_waiting = True self.post({'action': ACTION_INIT_REQUEST}) - _logger.debug('I joined a shared activity.') + for buddy in self.shared_activity.get_joined_buddies(): + self.buddy_joined.emit(buddy) + self.joined.emit() def _setup_text_channel(self): ''' Set up a text channel to use for collaboration. ''' + _logger.debug('_setup_text_channel') self._text_channel = _TextChannelWrapper( self.shared_activity.telepathy_text_chan, self.shared_activity.telepathy_conn) @@ -218,10 +260,12 @@ def _setup_text_channel(self): self.shared_activity.connect('buddy-left', self.__buddy_left_cb) def _listen_for_channels(self): + _logger.debug('_listen_for_channels') conn = self.shared_activity.telepathy_conn conn.connect_to_signal('NewChannels', self.__new_channels_cb) def __new_channels_cb(self, channels): + _logger.debug('__new_channels_cb') conn = self.shared_activity.telepathy_conn for path, props in channels: if props[CHANNEL + '.Requested']: @@ -232,39 +276,43 @@ def __new_channels_cb(self, channels): self._handle_ft_channel(conn, path, props) def _handle_ft_channel(self, conn, path, props): + _logger.debug('_handle_ft_channel') ft = IncomingFileTransfer(conn, path, props) if ft.description == ACTION_INIT_RESPONSE: - ft.connect('notify::state', self.__notify_ft_state_cb) + ft.connect('ready', self.__ready_cb) ft.accept_to_memory() else: desc = json.loads(ft.description) self.incoming_file.emit(ft, desc) - def __notify_ft_state_cb(self, ft, pspec): - if ft.props.state == FT_STATE_COMPLETED and self._init_waiting: - stream = ft.props.output + def __ready_cb(self, ft, stream): + _logger.debug('__ready_cb') + if self._init_waiting: stream.close(None) # FIXME: The data prop seems to just be the raw pointer gbytes = stream.steal_as_bytes() data = gbytes.get_data() - logging.debug('Got init data from buddy: %s', data) + _logger.debug('Got init data from buddy: %r', data) data = json.loads(data) self.activity.set_data(data) self._init_waiting = False def __received_cb(self, buddy, msg): '''Process a message when it is received.''' + _logger.debug('__received_cb') action = msg.get('action') - if action == ACTION_INIT_REQUEST and self._leader: - data = self.activity.get_data() - data = json.dumps(data) - OutgoingBlobTransfer( - buddy, - self.shared_activity.telepathy_conn, - data, - self.get_client_name(), - ACTION_INIT_RESPONSE, - ACTIVITY_FT_MIME) + if action == ACTION_INIT_REQUEST: + if self._leader: + data = self.activity.get_data() + if data is not None: + data = json.dumps(data) + OutgoingBlobTransfer( + buddy, + self.shared_activity.telepathy_conn, + data, + self.get_client_name(), + ACTION_INIT_RESPONSE, + ACTIVITY_FT_MIME) return if buddy: @@ -276,15 +324,16 @@ def __received_cb(self, buddy, msg): def send_file_memory(self, buddy, data, description): ''' - Send a 1-to-1 transfer from memory to a given buddy. They will - get the file transfer and description through the `incoming_transfer` - signal. + Send a one to one file transfer from memory to a buddy. The + buddy will get the file transfer and description through the + `incoming_transfer` signal. + Args: - buddy (sugar3.presence.buddy.Buddy), buddy to offer the transfer to - data (str), the data to offer to the buddy via the transfer + buddy (sugar3.presence.buddy.Buddy), buddy to send to. + data (str), the data to send. description (object), a json encodable description for the - transfer. This will be given to the `incoming_transfer` signal - of the transfer + transfer. This will be given to the + `incoming_transfer` signal at the buddy. ''' OutgoingBlobTransfer( buddy, @@ -296,15 +345,16 @@ def send_file_memory(self, buddy, data, description): def send_file_file(self, buddy, path, description): ''' - Send a 1-to-1 transfer from a file to a given buddy. They will - get the file transfer and description through the `incoming_transfer` - signal. + Send a one to one file transfer from a filesystem path to a + given buddy. The buddy will get the file transfer and + description through the `incoming_transfer` signal. + Args: - buddy (sugar3.presence.buddy.Buddy), buddy to offer the transfer to - path (str), path of the file to send to the buddy + buddy (sugar3.presence.buddy.Buddy), buddy to send to. + path (str), path of the file containing the data to send. description (object), a json encodable description for the - transfer. This will be given to the `incoming_transfer` signal - of the transfer + transfer. This will be given to the + `incoming_transfer` signal at the buddy. ''' OutgoingFileTransfer( buddy, @@ -316,12 +366,12 @@ def send_file_file(self, buddy, path, description): def post(self, msg): ''' - Broadcast a message to the other buddies if the activity is - shared. If it is not shared, the message will not be send - at all. + Send a message to all buddies. If the activity is not shared, + no message is sent. + Args: - msg (object): json encodable object to send to the other - buddies, eg. :class:`dict` or :class:`str`. + msg (object): json encodable object to send, + eg. :class:`dict` or :class:`str`. ''' if self._text_channel is not None: self._text_channel.post(msg) @@ -337,10 +387,27 @@ def __buddy_left_cb(self, sender, buddy): def get_client_name(self): ''' Get the name of the activity's telepathy client. + Returns: str, telepathy client name ''' return CLIENT + '.' + self.activity.get_bundle_id() + @GObject.Property + def leader(self): + ''' + Boolean of if this client is the leader in this activity. The + way the leader is decided may change, however there should only + ever be one leader for an activity. + ''' + return self._leader + + @GObject.Property + def owner(self): + ''' + Ourselves, :class:`sugar3.presence.buddy.Owner` + ''' + return self._owner + FT_STATE_NONE = 0 FT_STATE_PENDING = 1 @@ -362,6 +429,7 @@ class _BaseFileTransfer(GObject.GObject): ''' The base file transfer should not be used directly. It is used as a base class for the incoming and outgoing file transfers. + Props: filename (str), metadata provided by the buddy file_size (str), size of the file being sent/received, in bytes @@ -370,9 +438,10 @@ class _BaseFileTransfer(GObject.GObject): buddy (:class:`sugar3.presence.buddy.Buddy`), other party in the transfer reason_last_change (FT_REASON_*), reason for the last state change + GObject Props: state (FT_STATE_*), current state of the transfer - transferred_bytes (int), number of bytes transfered so far + transferred_bytes (int), number of bytes transferred so far ''' def __init__(self): @@ -401,7 +470,7 @@ def set_channel(self, channel): self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal( 'InitialOffsetDefined', self.__initial_offset_defined_cb) - channel_properties = self.channel[dbus.PROPERTIES_IFACE] + channel_properties = self.channel[PROPERTIES_IFACE] props = channel_properties.GetAll(CHANNEL_TYPE_FILE_TRANSFER) self._state = props['State'] @@ -411,7 +480,7 @@ def set_channel(self, channel): self.mime_type = props['ContentType'] def __transferred_bytes_changed_cb(self, transferred_bytes): - logging.debug('__transferred_bytes_changed_cb %r', transferred_bytes) + _logger.debug('__transferred_bytes_changed_cb %r', transferred_bytes) self.props.transferred_bytes = transferred_bytes def _set_transferred_bytes(self, transferred_bytes): @@ -426,11 +495,11 @@ def _get_transferred_bytes(self): setter=_set_transferred_bytes) def __initial_offset_defined_cb(self, offset): - logging.debug('__initial_offset_defined_cb %r', offset) + _logger.debug('__initial_offset_defined_cb %r', offset) self.initial_offset = offset def __state_changed_cb(self, state, reason): - logging.debug('__state_changed_cb %r %r', state, reason) + _logger.debug('__state_changed_cb %r %r', state, reason) self.reason_last_change = reason self.props.state = state @@ -445,6 +514,7 @@ def _get_state(self): def cancel(self): ''' Request that telepathy close the file transfer channel + Spec: http://telepathy.freedesktop.org/spec/Channel.html#Method:Close ''' self.channel[CHANNEL].Close() @@ -457,16 +527,24 @@ class IncomingFileTransfer(_BaseFileTransfer): to the state and wait until the transfer is completed. Then you can read the file that it was saved to, or access the :class:`Gio.MemoryOutputStream` from the `output` property. + The `output` property is different depending on how the file was accepted. If the file was accepted to a file on the file system, it is a string representing the path to the file. If the file was accepted to memory, it is a :class:`Gio.MemoryOutputStream`. ''' + ready = GObject.Signal('ready', arg_types=[object]) + def __init__(self, connection, object_path, props): _BaseFileTransfer.__init__(self) - channel = Channel(connection.bus_name, object_path) + channel = {} + proxy = dbus.Bus().get_object(connection.bus_name, object_path) + channel[PROPERTIES_IFACE] = dbus.Interface(proxy, PROPERTIES_IFACE) + channel[CHANNEL] = dbus.Interface(proxy, CHANNEL) + channel[CHANNEL_TYPE_FILE_TRANSFER] = dbus.Interface( + proxy, CHANNEL_TYPE_FILE_TRANSFER) self.set_channel(channel) self.connect('notify::state', self.__notify_state_cb) @@ -481,6 +559,7 @@ def accept_to_file(self, destination_path): ''' Accept the file transfer and write it to a new file. The file must already exist. + Args: destination_path (str): the path where a new file will be created and saved to @@ -497,6 +576,7 @@ def accept_to_memory(self): Accept the file transfer. Once the state is FT_STATE_OPEN, a :class:`Gio.MemoryOutputStream` accessible via the output prop. ''' + self._destination_path = None self._accept() def _accept(self): @@ -509,7 +589,7 @@ def _accept(self): byte_arrays=True) def __notify_state_cb(self, file_transfer, pspec): - logging.debug('__notify_state_cb %r', self.props.state) + _logger.debug('__notify_state_cb %r', self.props.state) if self.props.state == FT_STATE_OPEN: # Need to hold a reference to the socket so that python doesn't # close the fd when it goes out of scope @@ -526,13 +606,21 @@ def __notify_state_cb(self, file_transfer, pspec): else: self._output_stream = destination_file.append_to() else: - self._output_stream = Gio.MemoryOutputStream.new_resizable() + if hasattr(Gio.MemoryOutputStream, 'new_resizable'): + self._output_stream = \ + Gio.MemoryOutputStream.new_resizable() + else: + self._output_stream = Gio.MemoryOutputStream() self._output_stream.splice_async( input_stream, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE | Gio.OutputStreamSpliceFlags.CLOSE_TARGET, - GLib.PRIORITY_LOW, None, None, None) + GLib.PRIORITY_LOW, None, self.__splice_done_cb, None) + + def __splice_done_cb(self, output_stream, res, user): + _logger.debug('__splice_done_cb') + self.ready.emit(self._destination_path or self._output_stream) @GObject.Property def output(self): @@ -542,10 +630,12 @@ def output(self): class _BaseOutgoingTransfer(_BaseFileTransfer): ''' This class provides the base of an outgoing file transfer. + You can override the `_get_input_stream` method to return any type of Gio input stream. This will then be used to provide the file if requested by the application. You also need to call `_create_channel` with the length of the file in bytes during your `__init__`. + Args: buddy (sugar3.presence.buddy.Buddy), who to send the transfer to conn (telepathy.client.conn.Connection), telepathy connection to @@ -578,7 +668,13 @@ def _create_channel(self, file_size): CHANNEL_TYPE_FILE_TRANSFER + '.Size': file_size, CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': self._mime, CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0}, signature='sv')) - self.set_channel(Channel(self._conn.bus_name, object_path)) + channel = {} + proxy = dbus.Bus().get_object(self._conn.bus_name, object_path) + channel[PROPERTIES_IFACE] = dbus.Interface(proxy, PROPERTIES_IFACE) + channel[CHANNEL] = dbus.Interface(proxy, CHANNEL) + channel[CHANNEL_TYPE_FILE_TRANSFER] = dbus.Interface( + proxy, CHANNEL_TYPE_FILE_TRANSFER) + self.set_channel(channel) channel_file_transfer = self.channel[CHANNEL_TYPE_FILE_TRANSFER] self._socket_address = channel_file_transfer.ProvideFile( @@ -609,8 +705,10 @@ class OutgoingFileTransfer(_BaseOutgoingTransfer): ''' An outgoing file transfer to send from a file (on the computer's file system). + Note that the `path` argument is the path for the file that will be sent, whereas the `filename` argument is only for metadata. + Args: path (str), path of the file to send ''' @@ -624,13 +722,13 @@ def __init__(self, buddy, conn, path, filename, description, mime): self._create_channel(file_size) def _get_input_stream(self): - logging.debug('opening %s for reading', self._file_name) - input_stream = Gio.File.new_for_path(self._file_name).read(None) + return Gio.File.new_for_path(self._path).read(None) class OutgoingBlobTransfer(_BaseOutgoingTransfer): ''' An outgoing file transfer to send from a string in memory. + Args: blob (str), data to send ''' @@ -639,7 +737,7 @@ def __init__(self, buddy, conn, blob, filename, description, mime): _BaseOutgoingTransfer.__init__( self, buddy, conn, filename, description, mime) - self._blob = blob + self._blob = blob.encode('utf-8') self._create_channel(len(self._blob)) def _get_input_stream(self): @@ -693,6 +791,7 @@ def _closed_cb(self): def set_received_callback(self, callback): '''Connect the function callback to the signal. + callback -- callback function taking buddy and text args ''' if self._text_chan is None: @@ -711,6 +810,7 @@ def handle_pending_messages(self): def _received_cb(self, identity, timestamp, sender, type_, flags, text): '''Handle received text from the text channel. + Converts sender to a Buddy. Calls self._activity_cb which is a callback to the activity. ''' @@ -729,12 +829,12 @@ def _received_cb(self, identity, timestamp, sender, type_, flags, text): nick = self._conn[ CONN_INTERFACE_ALIASING].RequestAliases([sender])[0] buddy = {'nick': nick, 'color': '#000000,#808080'} - _logger.debug('exception: recieved from sender %r buddy %r' % + _logger.debug('exception: received from sender %r buddy %r' % (sender, buddy)) else: # XXX: cache these buddy = self._get_buddy(sender) - _logger.debug('Else: recieved from sender %r buddy %r' % + _logger.debug('Else: received from sender %r buddy %r' % (sender, buddy)) self._activity_cb(buddy, msg) @@ -747,7 +847,9 @@ def _received_cb(self, identity, timestamp, sender, type_, flags, text): def set_closed_callback(self, callback): '''Connect a callback for when the text channel is closed. + callback -- callback function taking no args + ''' _logger.debug('set closed callback') self._activity_close_cb = callback @@ -762,13 +864,14 @@ def _get_buddy(self, cs_handle): # Get the Telepathy Connection tp_name, tp_path = pservice.get_preferred_connection() - conn = Connection(tp_name, tp_path) + obj = dbus.Bus().get_object(tp_name, tp_path) + conn = dbus.Interface(obj, CONN_INTERFACE) group = self._text_chan[CHANNEL_INTERFACE_GROUP] my_csh = group.GetSelfHandle() if my_csh == cs_handle: handle = conn.GetSelfHandle() - elif (group.GetGroupFlags() & - CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES): + elif group.GetGroupFlags() & \ + CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES: handle = group.GetHandleOwners([cs_handle])[0] else: handle = cs_handle From 90dc15af69b26f5769252f2c974b624d85d62d8a Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Tue, 10 Mar 2020 18:10:33 +0530 Subject: [PATCH 2/7] port statvfs to os module --- utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils.py b/utils.py index a52649a..d22f34a 100644 --- a/utils.py +++ b/utils.py @@ -15,7 +15,6 @@ import subprocess import dbus import stat -import statvfs import glob import urllib from random import uniform @@ -508,8 +507,8 @@ def unexpected_training_data_files(path, name): def is_full(path, required=_MINIMUM_SPACE): ''' Make sure we have some room to write our data ''' volume_status = os.statvfs(path) - free_space = volume_status[statvfs.F_BSIZE] * \ - volume_status[statvfs.F_BAVAIL] + free_space = volume_status[os.statvfs().f_bsize] * \ + volume_status[os.statvfs().f_bavail] _logger.debug('free space: %d MB' % int(free_space / (1024 * 1024))) if free_space < required: _logger.error('free space: %d MB' % int(free_space / (1024 * 1024))) From 827696d199ad1868f8a30b83575ab4ea5840b449 Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Tue, 10 Mar 2020 18:15:00 +0530 Subject: [PATCH 3/7] Port from GConf to Gio.Settings --- activity.py | 1 - utils.py | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/activity.py b/activity.py index 79c8455..8309cb6 100644 --- a/activity.py +++ b/activity.py @@ -19,7 +19,6 @@ import gi gi.require_version('Gtk', '3.0') -gi.require_version('GConf', '2.0') gi.require_version('Vte', '2.91') gi.require_version('Wnck', '3.0') diff --git a/utils.py b/utils.py index d22f34a..e3684e3 100644 --- a/utils.py +++ b/utils.py @@ -31,7 +31,6 @@ from gi.repository import GdkPixbuf from gi.repository import Gtk from gi.repository import GLib -from gi.repository import GConf from gi.repository import GObject from sugar3 import env @@ -555,8 +554,9 @@ def get_battery_level(): def get_sound_level(): - client = GConf.Client.get_default() - return client.get_int('/desktop/sugar/sound/volume') + settings = Gio.Settings('org.sugarlabs.sound') + volume = settings.get_string('volume') + return volume def is_clipboard_text_available(): @@ -982,8 +982,10 @@ def get_launch_count(activity): def get_colors(): - client = GConf.Client.get_default() - return XoColor(client.get_string('/desktop/sugar/user/color')) + settings = Gio.Settings('org.sugarlabs.user') + path = '/desktop/sugar/user/color' + color = settings.get_string('color') + return XoColor(color) def get_nick(): From 5c28adb17064ce062c4c118a39352fc746a970fd Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Tue, 10 Mar 2020 18:28:25 +0530 Subject: [PATCH 4/7] Port to TelepathyGLib --- activity.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/activity.py b/activity.py index 8309cb6..f724631 100644 --- a/activity.py +++ b/activity.py @@ -41,10 +41,10 @@ from sugar3 import profile from sugar3.datastore import datastore -import telepathy +from gi.repository import TelepathyGLib import dbus from dbus.service import signal -from dbus.gobject_service import ExportedGObject +from dbus.gi_service import ExportedGObject from sugar3.presence import presenceservice from sugar3.graphics.objectchooser import ObjectChooser @@ -733,11 +733,11 @@ def _shared_cb(self, activity): self.tubes_chan = self.shared_activity.telepathy_tubes_chan self.text_chan = self.shared_activity.telepathy_text_chan - self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( + self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) _logger.debug('This is my activity: making a tube...') - self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) self.sharing = True @@ -761,11 +761,11 @@ def _joined_cb(self, activity): self.tubes_chan = self.shared_activity.telepathy_tubes_chan self.text_chan = self.shared_activity.telepathy_text_chan - self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( + self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) _logger.debug('I am joining an activity: waiting for a tube...') - self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( + self.tubes_chan[TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) @@ -786,10 +786,10 @@ def _new_tube_cb(self, id, initiator, type, service, params, state): 'params=%r state=%d', id, initiator, type, service, params, state) - if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): - if state == telepathy.TUBE_STATE_LOCAL_PENDING: + if (type == TelepathyGLib.IFACE_CHANNEL_TYPE_DBUS_TUBE and service == SERVICE): + if state == TelepathyGLib.TubeState.LOCAL_PENDING: self.tubes_chan[ - telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) + TelepathyGLib.IFACE_CHANNEL_TYPE_TUBES].AcceptDBusTube(id) self.collab = CollabWrapper(self) self.collab.message.connect(self.event_received_cb) From 2611019250de396025dc13a1f2ae54fca41424ec Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Tue, 10 Mar 2020 19:28:26 +0530 Subject: [PATCH 5/7] update repository metadata --- activity/activity.info | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activity/activity.info b/activity/activity.info index eae5519..2cf2af1 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -8,4 +8,4 @@ show_launcher = yes license = GPLv3 single_instance = yes summary = a tool for reflecting on your work -repository = https://github.com/walterbender/reflect +repository = https://github.com/sugarlabs/reflect.git From e4d559563aa5ecd4e3fbb223612b03f6f2bd9df6 Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Wed, 11 Mar 2020 02:18:05 +0530 Subject: [PATCH 6/7] changes made as requested get_int for volume instead of get_string removed path because it was used by GConf client --- utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils.py b/utils.py index e3684e3..64bae2b 100644 --- a/utils.py +++ b/utils.py @@ -555,7 +555,7 @@ def get_battery_level(): def get_sound_level(): settings = Gio.Settings('org.sugarlabs.sound') - volume = settings.get_string('volume') + volume = settings.get_int('volume') return volume @@ -983,7 +983,6 @@ def get_launch_count(activity): def get_colors(): settings = Gio.Settings('org.sugarlabs.user') - path = '/desktop/sugar/user/color' color = settings.get_string('color') return XoColor(color) From 6bc33bebbcaa74f0130b43e38e8501415606f7c8 Mon Sep 17 00:00:00 2001 From: ayushnawal Date: Wed, 18 Mar 2020 02:17:29 +0530 Subject: [PATCH 7/7] Port to Python 3 --- activity.py | 2 +- activity/activity.info | 2 +- reflectwindow.py | 2 +- utils.py | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/activity.py b/activity.py index f724631..4233721 100644 --- a/activity.py +++ b/activity.py @@ -13,7 +13,7 @@ import os import shutil import time -from ConfigParser import ConfigParser +from configparser import ConfigParser import json from gettext import gettext as _ diff --git a/activity/activity.info b/activity/activity.info index 2cf2af1..76219ae 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -1,6 +1,6 @@ [Activity] name = Reflect -exec = sugar-activity activity.ReflectActivity +exec = sugar-activity3 activity.ReflectActivity bundle_id = org.sugarlabs.Reflect icon = reflect activity_version = 1.0 diff --git a/reflectwindow.py b/reflectwindow.py index 4650de6..480c072 100644 --- a/reflectwindow.py +++ b/reflectwindow.py @@ -713,7 +713,7 @@ def _choose_activity(self): bundle_icons = utils.get_bundle_icons() x = 0 y = 1 - for bundle_id in bundle_icons.keys(): + for bundle_id in list(bundle_icons.keys()): icon_path = bundle_icons[bundle_id] if icon_path is None: continue diff --git a/utils.py b/utils.py index 64bae2b..38f7baa 100644 --- a/utils.py +++ b/utils.py @@ -16,14 +16,14 @@ import dbus import stat import glob -import urllib +import urllib.request, urllib.parse, urllib.error from random import uniform import tempfile import cairo import email.utils import re import time -from ConfigParser import ConfigParser +from configparser import ConfigParser from gi.repository import Vte from gi.repository import Gio @@ -358,7 +358,7 @@ def check_volume_suffix(volume_file): return TRAINING_DATA % volume_file[-13:] elif volume_file.endswith('.bin'): # See SEP-33 new_volume_file = volume_file[:-4] + TRAINING_SUFFIX - print new_volume_file + print(new_volume_file) os.rename(volume_file, new_volume_file) _logger.debug('return %s' % (TRAINING_DATA % new_volume_file[-13:])) return TRAINING_DATA % new_volume_file[-13:] @@ -532,7 +532,7 @@ def is_landscape(): def get_safe_text(text): - return urllib.pathname2url(text.encode('ascii', 'xmlcharrefreplace')) + return urllib.request.pathname2url(text.encode('ascii', 'xmlcharrefreplace')) def get_battery_level(): @@ -1095,7 +1095,7 @@ def uitree_dump(): try: return json.loads(dbus.Interface(proxy, _DBUS_SERVICE).Dump()) except Exception as e: - print ('ERROR calling Dump: %s' % e) + print('ERROR calling Dump: %s' % e) # _logger.error('ERROR calling Dump: %s' % e) return ''