[Git][gajim/gajim][master] 3 commits: Move httpupload into modules

Philipp Hörist gitlab at dev.gajim.org
Sat Jul 7 01:50:17 CEST 2018


Philipp Hörist pushed to branch master at gajim / gajim


Commits:
4bcdbde2 by Philipp Hörist at 2018-07-07T01:49:50+02:00
Move httpupload into modules

- - - - -
09aead3e by Philipp Hörist at 2018-07-07T01:49:50+02:00
Remove unused imports

- - - - -
3a3b3224 by Philipp Hörist at 2018-07-07T01:49:50+02:00
Fix requesting pubsub node config

Regression from refactoring

- - - - -


11 changed files:

- gajim/chat_control.py
- gajim/chat_control_base.py
- gajim/common/connection.py
- gajim/common/connection_handlers.py
- gajim/common/connection_handlers_events.py
- gajim/common/httpupload.py → gajim/common/modules/httpupload.py
- gajim/common/modules/pubsub.py
- gajim/config.py
- gajim/groupchat_control.py
- gajim/gui_interface.py
- gajim/server_info.py


Changes:

=====================================
gajim/chat_control.py
=====================================
--- a/gajim/chat_control.py
+++ b/gajim/chat_control.py
@@ -274,6 +274,7 @@ class ChatControl(ChatControlBase):
     def update_actions(self):
         win = self.parent_win.window
         online = app.account_is_connected(self.account)
+        con = app.connections[self.account]
 
         # Add to roster
         if not isinstance(self.contact, GC_Contact) \
@@ -297,7 +298,7 @@ class ChatControl(ChatControlBase):
         httpupload = win.lookup_action(
             'send-file-httpupload-' + self.control_id)
         httpupload.set_enabled(
-            online and app.connections[self.account].httpupload)
+            online and con.get_module('HTTPUpload').available)
 
         # Send file (Jingle)
         jingle_conditions = (


=====================================
gajim/chat_control_base.py
=====================================
--- a/gajim/chat_control_base.py
+++ b/gajim/chat_control_base.py
@@ -759,19 +759,19 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
                 # groupchat only supports httpupload on drag and drop
                 if httpupload.get_enabled():
                     # use httpupload
-                    con.check_file_before_transfer(
+                    con.get_module('HTTPUpload').check_file_before_transfer(
                         path, self.encryption, contact,
                         self.session, groupchat=True)
             else:
                 if httpupload.get_enabled() and jingle.get_enabled():
                     if ft_pref == 'httpupload':
-                        con.check_file_before_transfer(
+                        con.get_module('HTTPUpload').check_file_before_transfer(
                             path, self.encryption, contact, self.session)
                     else:
                         ft = app.interface.instances['file_transfers']
                         ft.send_file(self.account, contact, path)
                 elif httpupload.get_enabled():
-                    con.check_file_before_transfer(
+                    con.get_module('HTTPUpload').check_file_before_transfer(
                         path, self.encryption, contact, self.session)
                 elif jingle.get_enabled():
                     ft = app.interface.instances['file_transfers']


=====================================
gajim/common/connection.py
=====================================
--- a/gajim/common/connection.py
+++ b/gajim/common/connection.py
@@ -32,14 +32,12 @@
 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 ##
 
-import os
 import sys
 import random
 import socket
 import operator
 import string
 import time
-import locale
 import hmac
 import hashlib
 import json
@@ -83,6 +81,7 @@ from gajim.common.modules.user_tune import UserTune
 from gajim.common.modules.user_mood import UserMood
 from gajim.common.modules.user_location import UserLocation
 from gajim.common.modules.user_nickname import UserNickname
+from gajim.common.modules.httpupload import HTTPUpload
 from gajim.common.connection_handlers import *
 from gajim.common.contacts import GC_Contact
 from gajim.gtkgui_helpers import get_action
@@ -681,6 +680,7 @@ class Connection(CommonConnection, ConnectionHandlers):
         self.register_module('UserMood', UserMood, self)
         self.register_module('UserLocation', UserLocation, self)
         self.register_module('UserNickname', UserNickname, self)
+        self.register_module('HTTPUpload', HTTPUpload, self)
 
         app.ged.register_event_handler('privacy-list-received', ged.CORE,
             self._nec_privacy_list_received)


=====================================
gajim/common/connection_handlers.py
=====================================
--- a/gajim/common/connection_handlers.py
+++ b/gajim/common/connection_handlers.py
@@ -28,14 +28,9 @@
 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 ##
 
-import os
-import base64
-import binascii
 import operator
-import hashlib
 
-from time import (altzone, daylight, gmtime, localtime, strftime,
-        time as time_time, timezone, tzname)
+from time import time as time_time
 
 from gi.repository import GLib
 
@@ -44,20 +39,16 @@ from gajim.common import caps_cache as capscache
 
 from gajim.common import helpers
 from gajim.common import app
-from gajim.common import dataforms
 from gajim.common import jingle_xtls
-from gajim.common import configpaths
 from gajim.common.caps_cache import muc_caps_cache
 from gajim.common.commands import ConnectionCommands
 from gajim.common.protocol.caps import ConnectionCaps
 from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
 from gajim.common.protocol.bytestream import ConnectionIBBytestream
 from gajim.common.message_archiving import ConnectionArchive313
-from gajim.common.httpupload import ConnectionHTTPUpload
 from gajim.common.connection_handlers_events import *
 
 from gajim.common import ged
-from gajim.common import nec
 from gajim.common.nec import NetworkEvent
 from gajim.common.const import KindConstant
 
@@ -72,7 +63,6 @@ METACONTACTS_ARRIVED = 'metacontacts_arrived'
 ROSTER_ARRIVED = 'roster_arrived'
 DELIMITER_ARRIVED = 'delimiter_arrived'
 PRIVACY_ARRIVED = 'privacy_arrived'
-PEP_CONFIG = 'pep_config'
 
 
 class ConnectionDisco:
@@ -754,14 +744,12 @@ class ConnectionHandlersBase:
 class ConnectionHandlers(ConnectionArchive313,
 ConnectionSocks5Bytestream, ConnectionDisco,
 ConnectionCommands, ConnectionCaps,
-ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream,
-ConnectionHTTPUpload):
+ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
     def __init__(self):
         ConnectionArchive313.__init__(self)
         ConnectionSocks5Bytestream.__init__(self)
         ConnectionIBBytestream.__init__(self)
         ConnectionCommands.__init__(self)
-        ConnectionHTTPUpload.__init__(self)
 
         # Handle presences BEFORE caps
         app.nec.register_incoming_event(PresenceReceivedEvent)
@@ -819,7 +807,6 @@ ConnectionHTTPUpload):
         ConnectionHandlersBase.cleanup(self)
         ConnectionCaps.cleanup(self)
         ConnectionArchive313.cleanup(self)
-        ConnectionHTTPUpload.cleanup(self)
         app.ged.remove_event_handler('roster-set-received',
             ged.CORE, self._nec_roster_set_received)
         app.ged.remove_event_handler('roster-received', ged.CORE,
@@ -944,21 +931,6 @@ ConnectionHTTPUpload):
                 # connection process, we don't take the risk
                 self.privacy_rules_supported = False
                 self._continue_connection_request_privacy()
-        elif self.awaiting_answers[id_][0] == PEP_CONFIG:
-            del self.awaiting_answers[id_]
-            if iq_obj.getType() == 'error':
-                return
-            if not iq_obj.getTag('pubsub'):
-                return
-            conf = iq_obj.getTag('pubsub').getTag('configure')
-            if not conf:
-                return
-            node = conf.getAttr('node')
-            form_tag = conf.getTag('x', namespace=nbxmpp.NS_DATA)
-            if form_tag:
-                form = dataforms.ExtendForm(node=form_tag)
-                app.nec.push_incoming_event(PEPConfigReceivedEvent(None,
-                    conn=self, node=node, form=form))
 
     def _nec_iq_error_received(self, obj):
         if obj.conn.name != self.name:


=====================================
gajim/common/connection_handlers_events.py
=====================================
--- a/gajim/common/connection_handlers_events.py
+++ b/gajim/common/connection_handlers_events.py
@@ -1822,10 +1822,6 @@ class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent):
     def generate(self):
         return True
 
-class PEPConfigReceivedEvent(nec.NetworkIncomingEvent):
-    name = 'pep-config-received'
-    base_network_events = []
-
 class MetacontactsReceivedEvent(nec.NetworkIncomingEvent):
     name = 'metacontacts-received'
     base_network_events = []
@@ -2545,17 +2541,3 @@ class BlockingEvent(nec.NetworkIncomingEvent):
             app.log('blocking').info(
                 'Blocking Push - unblocked JIDs: %s', self.unblocked_jids)
         return True
-
-class HTTPUploadStartEvent(nec.NetworkIncomingEvent):
-    name = 'httpupload-start'
-    base_network_events = []
-
-    def generate(self):
-        return True
-
-class HTTPUploadProgressEvent(nec.NetworkIncomingEvent):
-    name = 'httpupload-progress'
-    base_network_events = []
-
-    def generate(self):
-        return True
\ No newline at end of file


=====================================
gajim/common/httpupload.py → gajim/common/modules/httpupload.py
=====================================
--- a/gajim/common/httpupload.py
+++ b/gajim/common/modules/httpupload.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
 # This file is part of Gajim.
 #
 # Gajim is free software; you can redistribute it and/or modify
@@ -14,13 +12,16 @@
 # You should have received a copy of the GNU General Public License
 # along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
 
+# XEP-0363: HTTP File Upload
+
+
 import os
 import sys
 import threading
 import ssl
 import urllib
 from urllib.request import Request, urlopen
-from urllib.parse import urlparse, quote
+from urllib.parse import urlparse
 import io
 import mimetypes
 import logging
@@ -31,26 +32,28 @@ from gi.repository import GLib
 
 from gajim.common import app
 from gajim.common import ged
+from gajim.common.nec import NetworkIncomingEvent
 from gajim.common.connection_handlers_events import InformationEvent
-from gajim.common.connection_handlers_events import HTTPUploadProgressEvent
 from gajim.common.connection_handlers_events import MessageOutgoingEvent
 from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
 
 if sys.platform in ('win32', 'darwin'):
     import certifi
 
-log = logging.getLogger('gajim.c.httpupload')
+log = logging.getLogger('gajim.c.m.httpupload')
+
 
 NS_HTTPUPLOAD_0 = NS_HTTPUPLOAD + ':0'
 
-class ConnectionHTTPUpload:
-    """
-    Implement HTTP File Upload
-    (XEP-0363, https://xmpp.org/extensions/xep-0363.html)
-    """
-    def __init__(self):
-        self.httpupload = False
-        self.encrypted_upload = False
+
+class HTTPUpload:
+    def __init__(self, con):
+        self._con = con
+        self._account = con.name
+
+        self.handlers = []
+
+        self.available = False
         self.component = None
         self.httpupload_namespace = None
         self._allowed_headers = ['Authorization', 'Cookie', 'Expires']
@@ -81,7 +84,7 @@ class ConnectionHTTPUpload:
 
     def handle_agent_info_received(self, event):
         account = event.conn.name
-        if account != self.name:
+        if account != self._account:
             return
 
         if not app.jid_is_transport(event.jid):
@@ -112,14 +115,14 @@ class ConnectionHTTPUpload:
             log.warning('%s does not provide maximum file size', account)
         else:
             log.info('%s has a maximum file size of: %s MiB',
-                     account, self.max_file_size/(1024*1024))
+                     account, self.max_file_size / (1024 * 1024))
 
-        self.httpupload = True
-        for ctrl in app.interface.msg_win_mgr.get_controls(acct=self.name):
+        self.available = True
+        for ctrl in app.interface.msg_win_mgr.get_controls(acct=self._account):
             ctrl.update_actions()
 
     def handle_outgoing_stanza(self, event):
-        if event.conn.name != self.name:
+        if event.conn.name != self._account:
             return
         message = event.msg_iq.getTagData('body')
         if message and message in self.messages:
@@ -177,9 +180,9 @@ class ConnectionHTTPUpload:
             return
 
         if encryption is not None:
-            app.interface.encrypt_file(file, self.request_slot)
+            app.interface.encrypt_file(file, self._request_slot)
         else:
-            self.request_slot(file)
+            self._request_slot(file)
 
     @staticmethod
     def raise_progress_event(status, file, seen=None, total=None):
@@ -191,13 +194,13 @@ class ConnectionHTTPUpload:
         app.nec.push_incoming_event(InformationEvent(
             None, dialog_name=dialog_name, args=args))
 
-    def request_slot(self, file):
+    def _request_slot(self, file):
         GLib.idle_add(self.raise_progress_event, 'request', file)
         iq = self._build_request(file)
         log.info("Sending request for slot")
-        app.connections[self.name].connection.SendAndCallForResponse(
-            iq, self.received_slot, {'file': file})
-        
+        self._con.connection.SendAndCallForResponse(
+            iq, self._received_slot, {'file': file})
+
     def _build_request(self, file):
         iq = nbxmpp.Iq(typ='get', to=self.component)
         id_ = app.get_an_id()
@@ -230,7 +233,7 @@ class ConnectionHTTPUpload:
 
         return stanza.getErrorMsg()
 
-    def received_slot(self, conn, stanza, file):
+    def _received_slot(self, conn, stanza, file):
         log.info("Received slot")
         if stanza.getType() == 'error':
             self.raise_progress_event('close', file)
@@ -279,11 +282,11 @@ class ConnectionHTTPUpload:
         log.info('Uploading file to %s', file.put)
         log.info('Please download from %s', file.get)
 
-        thread = threading.Thread(target=self.upload_file, args=(file,))
+        thread = threading.Thread(target=self._upload_file, args=(file,))
         thread.daemon = True
         thread.start()
 
-    def upload_file(self, file):
+    def _upload_file(self, file):
         GLib.idle_add(self.raise_progress_event, 'upload', file)
         try:
             file.headers['User-Agent'] = 'Gajim %s' % app.version
@@ -294,7 +297,7 @@ class ConnectionHTTPUpload:
                 file.put, data=file.stream, headers=file.headers, method='PUT')
             log.info("Opening Urllib upload request...")
 
-            if not app.config.get_per('accounts', self.name, 'httpupload_verify'):
+            if not app.config.get_per('accounts', self._account, 'httpupload_verify'):
                 context = ssl.create_default_context()
                 context.check_hostname = False
                 context.verify_mode = ssl.CERT_NONE
@@ -309,7 +312,7 @@ class ConnectionHTTPUpload:
             file.stream.close()
             log.info('Urllib upload request done, response code: %s',
                      transfer.getcode())
-            GLib.idle_add(self.upload_complete, transfer.getcode(), file)
+            GLib.idle_add(self._upload_complete, transfer.getcode(), file)
             return
         except UploadAbortedException as exc:
             log.info(exc)
@@ -326,9 +329,9 @@ class ConnectionHTTPUpload:
             log.exception("Exception during upload")
             error_msg = exc
         GLib.idle_add(self.raise_progress_event, 'close', file)
-        GLib.idle_add(self.on_upload_error, file, error_msg)
+        GLib.idle_add(self._on_upload_error, file, error_msg)
 
-    def upload_complete(self, response_code, file):
+    def _upload_complete(self, response_code, file):
         self.raise_progress_event('close', file)
         if 200 <= response_code < 300:
             log.info("Upload completed successfully")
@@ -341,12 +344,12 @@ class ConnectionHTTPUpload:
 
             if file.groupchat:
                 app.nec.push_outgoing_event(GcMessageOutgoingEvent(
-                    None, account=self.name, jid=file.contact.jid,
+                    None, account=self._account, jid=file.contact.jid,
                     message=message, automatic_message=False,
                     session=file.session))
             else:
                 app.nec.push_outgoing_event(MessageOutgoingEvent(
-                    None, account=self.name, jid=file.contact.jid,
+                    None, account=self._account, jid=file.contact.jid,
                     message=message, keyID=file.keyID, type_='chat',
                     automatic_message=False, session=file.session))
 
@@ -356,7 +359,7 @@ class ConnectionHTTPUpload:
             self.raise_information_event('httpupload-response-error',
                                          response_code)
 
-    def on_upload_error(self, file, reason):
+    def _on_upload_error(self, file, reason):
         self.raise_progress_event('close', file)
         self.raise_information_event('httpupload-error', str(reason))
 
@@ -428,3 +431,8 @@ class StreamFileWithProgress:
 class UploadAbortedException(Exception):
     def __str__(self):
         return "Upload Aborted"
+
+
+class HTTPUploadProgressEvent(NetworkIncomingEvent):
+    name = 'httpupload-progress'
+    base_network_events = []


=====================================
gajim/common/modules/pubsub.py
=====================================
--- a/gajim/common/modules/pubsub.py
+++ b/gajim/common/modules/pubsub.py
@@ -25,6 +25,8 @@ import logging
 import nbxmpp
 
 from gajim.common import app
+from gajim.common import dataforms
+from gajim.common.nec import NetworkIncomingEvent
 
 log = logging.getLogger('gajim.c.m.pubsub')
 
@@ -185,7 +187,7 @@ class PubSub:
 
         self._con.connection.SendAndCallForResponse(query, cb)
 
-    def send_pb_configure(self, jid, node, form, cb=None):
+    def send_pb_configure(self, jid, node, form, cb=None, **kwargs):
         if not app.account_is_connected(self._account):
             return
 
@@ -197,21 +199,58 @@ class PubSub:
         c = c.addChild('configure', {'node': node})
         c.addChild(node=form)
 
-        self._con.connection.SendAndCallForResponse(query, cb)
+        log.info('Send node config for %s', node)
+        self._con.connection.SendAndCallForResponse(query, cb, kwargs)
 
-    def request_pb_configuration(self, jid, node, cb=None):
+    def request_pb_configuration(self, jid, node):
         if not app.account_is_connected(self._account):
             return
 
-        if cb is None:
-            cb = self._default_callback
-
         query = nbxmpp.Iq('get', to=jid)
         e = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
         e = e.addChild('configure', {'node': node})
 
-        self._con.connection.SendAndCallForResponse(query, cb)
+        log.info('Request node config for %s', node)
+        self._con.connection.SendAndCallForResponse(
+            query, self._received_pb_configuration, {'node': node})
+
+    def _received_pb_configuration(self, conn, stanza, node):
+        if not nbxmpp.isResultNode(stanza):
+            log.warning('Error: %s', stanza.getError())
+            return
+
+        pubsub = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
+        if pubsub is None:
+            log.warning('Malformed PubSub configure '
+                        'stanza (no pubsub node): %s', stanza)
+            return
+
+        configure = pubsub.getTag('configure')
+        if configure is None:
+            log.warning('Malformed PubSub configure '
+                        'stanza (no configure node): %s', stanza)
+            return
+
+        if configure.getAttr('node') != node:
+            log.warning('Malformed PubSub configure '
+                        'stanza (wrong node): %s', stanza)
+            return
+
+        form = configure.getTag('x', namespace=nbxmpp.NS_DATA)
+        if form is None:
+            log.warning('Malformed PubSub configure '
+                        'stanza (no form): %s', stanza)
+            return
+
+        app.nec.push_incoming_event(PubSubConfigReceivedEvent(
+            None, conn=self._con, node=node,
+            form=dataforms.ExtendForm(node=form)))
 
     def _default_callback(self, conn, stanza, *args, **kwargs):
         if not nbxmpp.isResultNode(stanza):
             log.warning('Error: %s', stanza.getError())
+
+
+class PubSubConfigReceivedEvent(NetworkIncomingEvent):
+    name = 'pubsub-config-received'
+    base_network_events = []


=====================================
gajim/config.py
=====================================
--- a/gajim/config.py
+++ b/gajim/config.py
@@ -2839,7 +2839,7 @@ class ManagePEPServicesWindow:
         self.xml.get_object('services_treeview').get_selection().connect(
                 'changed', self.on_services_selection_changed)
 
-        app.ged.register_event_handler('pep-config-received', ged.GUI1,
+        app.ged.register_event_handler('pubsub-config-received', ged.GUI1,
             self._nec_pep_config_received)
         app.ged.register_event_handler('agent-items-received', ged.GUI1,
             self._nec_agent_items_received)
@@ -2849,7 +2849,7 @@ class ManagePEPServicesWindow:
     def on_manage_pep_services_window_destroy(self, widget):
         '''close window'''
         del app.interface.instances[self.account]['pep_services']
-        app.ged.remove_event_handler('pep-config-received', ged.GUI1,
+        app.ged.remove_event_handler('pubsub-config-received', ged.GUI1,
             self._nec_pep_config_received)
         app.ged.remove_event_handler('agent-items-received', ged.GUI1,
             self._nec_agent_items_received)


=====================================
gajim/groupchat_control.py
=====================================
--- a/gajim/groupchat_control.py
+++ b/gajim/groupchat_control.py
@@ -574,6 +574,7 @@ class GroupchatControl(ChatControlBase):
         contact = app.contacts.get_gc_contact(
             self.account, self.room_jid, self.nick)
         online = app.gc_connected[self.account][self.room_jid]
+        con = app.connections[self.account]
 
         # Destroy Room
         win.lookup_action('destroy-' + self.control_id).set_enabled(
@@ -615,7 +616,7 @@ class GroupchatControl(ChatControlBase):
         httpupload = win.lookup_action(
             'send-file-httpupload-' + self.control_id)
         httpupload.set_enabled(
-            online and app.connections[self.account].httpupload)
+            online and con.get_module('HTTPUpload').available)
         win.lookup_action('send-file-' + self.control_id).set_enabled(
             httpupload.get_enabled())
 


=====================================
gajim/gui_interface.py
=====================================
--- a/gajim/gui_interface.py
+++ b/gajim/gui_interface.py
@@ -93,8 +93,9 @@ from gajim.common import passwords
 from gajim.common import logging_helpers
 from gajim.common.connection_handlers_events import (
     OurShowEvent, FileRequestErrorEvent, FileTransferCompletedEvent,
-    UpdateRosterAvatarEvent, UpdateGCAvatarEvent, UpdateRoomAvatarEvent,
-    HTTPUploadProgressEvent)
+    UpdateRosterAvatarEvent, UpdateGCAvatarEvent, UpdateRoomAvatarEvent)
+
+from gajim.common.modules.httpupload import HTTPUploadProgressEvent
 from gajim.common.connection import Connection
 from gajim.common.file_props import FilesProp
 from gajim import emoticons
@@ -1142,11 +1143,12 @@ class Interface:
         con = app.connections[chat_control.account]
         groupchat = chat_control.type_id == message_control.TYPE_GC
         for path in paths:
-            con.check_file_before_transfer(path,
-                                           chat_control.encryption,
-                                           chat_control.contact,
-                                           chat_control.session,
-                                           groupchat)
+            con.get_module('HTTPUpload').check_file_before_transfer(
+                path,
+                chat_control.encryption,
+                chat_control.contact,
+                chat_control.session,
+                groupchat)
 
     def encrypt_file(self, file, callback):
         app.nec.push_incoming_event(HTTPUploadProgressEvent(


=====================================
gajim/server_info.py
=====================================
--- a/gajim/server_info.py
+++ b/gajim/server_info.py
@@ -177,7 +177,8 @@ class ServerInfoDialog(Gtk.Dialog):
                     con.archiving_namespace, con.archiving_namespace,
                     mam_enabled),
             Feature('XEP-0363: HTTP File Upload',
-                    con.httpupload, con.httpupload_namespace, None)]
+                    con.get_module('HTTPUpload').available,
+                    con.get_module('HTTPUpload').httpupload_namespace, None)]
 
     def add_info(self, info):
         self.info_listbox.add(ServerInfoItem(info))



View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/0eeb111a02d23fd3dfc39f7e40620fb76ebaddb4...3a3b32249c918cad939af03d0115f8af513faf3d

-- 
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/0eeb111a02d23fd3dfc39f7e40620fb76ebaddb4...3a3b32249c918cad939af03d0115f8af513faf3d
You're receiving this email because of your account on dev.gajim.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.gajim.org/pipermail/commits/attachments/20180707/23c0583b/attachment-0001.html>


More information about the Commits mailing list