Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
520beb18 by lovetox at 2020-03-27T16:02:11+01:00
Add RSM (XEP-0059) support
- - - - -
b0038b28 by lovetox at 2020-03-28T07:44:16+01:00
Add MAM (XEP-0313) support
- - - - -
80ec5753 by lovetox at 2020-03-28T08:08:57+01:00
TCP: Initiate new read before data-received signal
- - - - -
5 changed files:
- nbxmpp/dispatcher.py
- + nbxmpp/modules/mam.py
- + nbxmpp/modules/rsm.py
- nbxmpp/structs.py
- nbxmpp/tcp.py
Changes:
=====================================
nbxmpp/dispatcher.py
=====================================
@@ -76,6 +76,7 @@ from nbxmpp.modules.security_labels import SecurityLabels
from nbxmpp.modules.chatstates import Chatstates
from nbxmpp.modules.register import Register
from nbxmpp.modules.http_upload import HTTPUpload
+from nbxmpp.modules.mam import MAM
from nbxmpp.modules.misc import unwrap_carbon
from nbxmpp.modules.misc import unwrap_mam
from nbxmpp.structs import StanzaTimeoutError
@@ -180,6 +181,7 @@ class StanzaDispatcher(Observable):
self._modules['Chatstates'] = Chatstates(self._client)
self._modules['Register'] = Register(self._client)
self._modules['HTTPUpload'] = HTTPUpload(self._client)
+ self._modules['MAM'] = MAM(self._client)
for instance in self._modules.values():
for handler in instance.handlers:
=====================================
nbxmpp/modules/mam.py
=====================================
@@ -0,0 +1,209 @@
+# Copyright (C) 2020 Philipp Hörist <philipp AT hoerist.com>
+#
+# This file is part of nbxmpp.
+#
+# 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 3
+# 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, see <http://www.gnu.org/licenses/>.
+
+
+from nbxmpp.protocol import JID
+from nbxmpp.protocol import Iq
+from nbxmpp.protocol import isResultNode
+from nbxmpp.protocol import Node
+from nbxmpp.protocol import NS_MAM_2
+from nbxmpp.protocol import NS_RSM
+from nbxmpp.structs import MAMQueryData
+from nbxmpp.structs import MAMPreferencesData
+from nbxmpp.structs import CommonResult
+from nbxmpp.util import call_on_response
+from nbxmpp.util import callback
+from nbxmpp.util import raise_error
+from nbxmpp.modules.rsm import parse_rsm
+from nbxmpp.modules.dataforms import SimpleDataForm
+from nbxmpp.modules.dataforms import create_field
+from nbxmpp.modules.base import BaseModule
+
+
+class MAM(BaseModule):
+ def __init__(self, client):
+ BaseModule.__init__(self, client)
+
+ self._client = client
+ self.handlers = []
+
+ @call_on_response('_query_result')
+ def make_query(self,
+ jid,
+ queryid,
+ start=None,
+ end=None,
+ with_=None,
+ after=None,
+ max_=70):
+
+ iq = Iq(typ='set', to=jid, queryNS=NS_MAM_2)
+ iq.getQuery().setAttr('queryid', queryid)
+
+ payload = [
+ self._make_query_form(start, end, with_),
+ self._make_rsm_query(max_, after)
+ ]
+
+ iq.setQueryPayload(payload)
+ return iq
+
+ @staticmethod
+ def _make_query_form(start, end, with_):
+ fields = [
+ create_field(typ='hidden', var='FORM_TYPE', value=NS_MAM_2)
+ ]
+
+ if start:
+ fields.append(create_field(
+ typ='text-single',
+ var='start',
+ value=start.strftime('%Y-%m-%dT%H:%M:%SZ')))
+
+ if end:
+ fields.append(create_field(
+ typ='text-single',
+ var='end',
+ value=end.strftime('%Y-%m-%dT%H:%M:%SZ')))
+
+ if with_:
+ fields.append(create_field(
+ typ='jid-single',
+ var='with',
+ value=with_))
+
+ return SimpleDataForm(type_='submit', fields=fields)
+
+ @staticmethod
+ def _make_rsm_query(max_, after):
+ rsm_set = Node('set', attrs={'xmlns': NS_RSM})
+ if max_ is not None:
+ rsm_set.setTagData('max', max_)
+ if after is not None:
+ rsm_set.setTagData('after', after)
+ return rsm_set
+
+ @callback
+ def _query_result(self, stanza):
+ if not isResultNode(stanza):
+ return raise_error(self._log.info, stanza)
+
+ jid = stanza.getFrom()
+ fin = stanza.getTag('fin', namespace=NS_MAM_2)
+ if fin is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'No fin node found')
+
+ rsm = parse_rsm(fin)
+ if rsm is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'rsm set missing')
+
+ complete = bool(fin.getAttr('complete'))
+
+ return MAMQueryData(jid=jid,
+ complete=complete,
+ rsm=rsm)
+
+ @call_on_response('_preferences_result')
+ def request_preferences(self):
+ iq = Iq('get', queryNS=NS_MAM_2)
+ iq.setQuery('prefs')
+ return iq
+
+ @callback
+ def _preferences_result(self, stanza):
+ if not isResultNode(stanza):
+ return raise_error(self._log.info, stanza)
+
+ prefs = stanza.getTag('prefs', namespace=NS_MAM_2)
+ if prefs is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'No prefs node found')
+
+ default = prefs.getAttr('default')
+ if default is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'No default attr found')
+
+ always_node = prefs.getTag('always')
+ if always_node is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'No always node found')
+
+ always = self._get_preference_jids(always_node)
+
+ never_node = prefs.getTag('never')
+ if never_node is None:
+ return raise_error(self._log.warning,
+ stanza,
+ 'stanza-malformed',
+ 'No never node found')
+
+ never = self._get_preference_jids(never_node)
+ return MAMPreferencesData(default=default,
+ always=always,
+ never=never)
+
+ def _get_preference_jids(self, node):
+ jids = []
+ for item in node.getTags('jid'):
+ jid = item.getData()
+ if not jid:
+ continue
+
+ try:
+ jid = JID(jid)
+ except Exception:
+ self._log.warning('Invalid jid found in preferences: %s',
+ jid)
+ jids.append(jid)
+ return jids
+
+ @call_on_response('_default_response')
+ def set_preferences(self, default, always, never):
+ if default not in ('always', 'never', 'roster'):
+ raise ValueError('Wrong default preferences type')
+
+ iq = Iq(typ='set')
+ prefs = iq.addChild(name='prefs',
+ namespace=NS_MAM_2,
+ attrs={'default': default})
+ always_node = prefs.addChild(name='always')
+ never_node = prefs.addChild(name='never')
+ for jid in always:
+ always_node.addChild(name='jid').setData(jid)
+
+ for jid in never:
+ never_node.addChild(name='jid').setData(jid)
+ return iq
+
+ @callback
+ def _default_response(self, stanza):
+ if not isResultNode(stanza):
+ return raise_error(self._log.info, stanza)
+ return CommonResult(jid=stanza.getFrom())
=====================================
nbxmpp/modules/rsm.py
=====================================
@@ -0,0 +1,62 @@
+# Copyright (C) 2020 Philipp Hörist <philipp AT hoerist.com>
+#
+# This file is part of nbxmpp.
+#
+# 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 3
+# 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, see <http://www.gnu.org/licenses/>.
+
+from nbxmpp.protocol import NS_RSM
+
+from nbxmpp.structs import RSMData
+
+
+def parse_rsm(stanza):
+ set_ = stanza.getTag('set', namespace=NS_RSM)
+ if set_ is None:
+ return None
+
+ after = stanza.getTagData('after') or None
+ before = stanza.getTagData('before') or None
+ last = stanza.getTagData('last') or None
+
+ first_index = None
+ first = stanza.getTagData('first') or None
+ if first is not None:
+ try:
+ first_index = int(first.getAttr('index'))
+ except Exception:
+ pass
+
+ try:
+ count = int(stanza.getTagData('count'))
+ except Exception:
+ count = None
+
+ try:
+ max_ = int(stanza.getTagData('max'))
+ except Exception:
+ max_ = None
+
+ try:
+ index = int(stanza.getTagData('index'))
+ except Exception:
+ index = None
+
+ return RSMData(after=after,
+ before=before,
+ last=last,
+ first=first,
+ first_index=first_index,
+ count=count,
+ max=max_,
+ index=index)
=====================================
nbxmpp/structs.py
=====================================
@@ -144,6 +144,12 @@ ChangePasswordResult.__new__.__defaults__ = (None,)
HTTPUploadData = namedtuple('HTTPUploadData', 'put_uri get_uri headers')
HTTPUploadData.__new__.__defaults__ = (None,)
+RSMData = namedtuple('RSMData', 'after before last first first_index count max index')
+
+MAMQueryData = namedtuple('MAMQueryData', 'jid rsm complete')
+
+MAMPreferencesData = namedtuple('MAMPreferencesData', 'default always never')
+
class DiscoInfo(namedtuple('DiscoInfo', 'stanza identities features dataforms timestamp')):
=====================================
nbxmpp/tcp.py
=====================================
@@ -236,13 +236,13 @@ class TCPConnection(Connection):
self._log_stanza(data, received=True)
+ self._read_async()
+
try:
self.notify('data-received', data)
except Exception:
self._log.exception('Error while executing data-received:')
- self._read_async()
-
def _write_stanzas(self):
self._write_stanza_buffer = self._write_queue
self._write_queue = deque([])
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/compare/ea902b3b0e7afe0bf16910f…
--
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/compare/ea902b3b0e7afe0bf16910f…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
ea902b3b by lovetox at 2020-03-25T23:22:35+01:00
Dispatcher: Remove useless code
- - - - -
1 changed file:
- nbxmpp/dispatcher.py
Changes:
=====================================
nbxmpp/dispatcher.py
=====================================
@@ -478,9 +478,6 @@ class StanzaDispatcher(Observable):
for id_, data in timeouts.items():
func, timeout, user_data = data
- if timeout is None:
- continue
- has_timeout = True
if user_data is None:
user_data = {}
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/ea902b3b0e7afe0bf16910ff…
--
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/ea902b3b0e7afe0bf16910ff…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
744b7baa by lovetox at 2020-03-25T23:20:33+01:00
Dispatcher: Refactor IQ timeout code
- - - - -
1 changed file:
- nbxmpp/dispatcher.py
Changes:
=====================================
nbxmpp/dispatcher.py
=====================================
@@ -459,23 +459,28 @@ class StanzaDispatcher(Observable):
def add_callback_for_id(self, id_, func, timeout, user_data):
if timeout is not None and self._timeout_id is None:
- self._log.info('Add timeout source')
+ self._log.info('Add timeout check')
self._timeout_id = GLib.timeout_add_seconds(
1, self._timeout_check)
timeout = time.monotonic() + timeout
self._id_callbacks[id_] = (func, timeout, user_data)
def _timeout_check(self):
- self._log.info('Run timeout check')
- if not self._id_callbacks:
- self._log.info('Remove timeout source, no callbacks scheduled')
+ timeouts = {}
+ for id_, data in self._id_callbacks.items():
+ if data[1] is not None:
+ timeouts[id_] = data
+
+ if not timeouts:
+ self._log.info('Remove timeout check, no timeouts scheduled')
self._timeout_id = None
return False
- for id_ in list(self._id_callbacks.keys()):
- func, timeout, user_data = self._id_callbacks.get(id_)
+ for id_, data in timeouts.items():
+ func, timeout, user_data = data
if timeout is None:
continue
+ has_timeout = True
if user_data is None:
user_data = {}
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/744b7baa7af858d5b312ee74…
--
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/744b7baa7af858d5b312ee74…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
42a4409f by lovetox at 2020-03-25T21:23:12+01:00
Client: Don't reset ping source on pong
Fixes #104
- - - - -
1 changed file:
- nbxmpp/client.py
Changes:
=====================================
nbxmpp/client.py
=====================================
@@ -777,7 +777,6 @@ class Client(Observable):
self._log.info('Ping')
def _on_pong(self, _client, result):
- self._ping_source_id = None
if is_error_result(result):
if result.condition == 'timeout':
self._log.info('Ping timeout')
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/42a4409ffa7598269145c1ef…
--
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/commit/42a4409ffa7598269145c1ef…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits:
979831bf by lovetox at 2020-03-24T22:24:23+01:00
Client: Remove ping timer on stream end
- - - - -
a2804302 by lovetox at 2020-03-24T22:30:51+01:00
Client: Handle server not ending stream gracefully
- - - - -
1b04b14d by lovetox at 2020-03-24T22:31:05+01:00
Client: Remove comment
- - - - -
2 changed files:
- nbxmpp/client.py
- nbxmpp/tcp.py
Changes:
=====================================
nbxmpp/client.py
=====================================
@@ -52,8 +52,6 @@ from nbxmpp.util import LogAdapter
log = logging.getLogger('nbxmpp.stream')
-# TODO: check if signals make sense
-
class Client(Observable):
def __init__(self, log_context=None):
@@ -448,6 +446,7 @@ class Client(Observable):
self._con.shutdown_input()
if not self._stream_close_initiated:
self.state = StreamState.DISCONNECTING
+ self._remove_ping_timer()
self._smacks.close_session()
self._end_stream()
self._con.shutdown_output()
=====================================
nbxmpp/tcp.py
=====================================
@@ -269,6 +269,10 @@ class TCPConnection(Connection):
self._write_stanza_buffer = None
return
+ if self._output_closed:
+ self._check_for_shutdown()
+ return
+
self._log.error('Write Error: %s', error)
return
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/compare/93e0a046cbc5e14e08b5ebe…
--
View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/-/compare/93e0a046cbc5e14e08b5ebe…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / gajim
Commits:
1abe86fa by lovetox at 2020-03-24T17:07:53+01:00
Roster: Show correct account row status
The row was initialized with the show value instead of using
the helper function get_connection_status()
Fixes #10035
- - - - -
1 changed file:
- gajim/roster_window.py
Changes:
=====================================
gajim/roster_window.py
=====================================
@@ -290,7 +290,7 @@ class RosterWindow:
None, None, None, True] + [None] * self.nb_ext_renderers)
self._iters['MERGED']['account'] = it
else:
- show = app.connections[account].status
+ show = helpers.get_connection_status(account)
our_jid = app.get_jid_from_account(account)
tls_pixbuf = None
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/1abe86fa362eb12ce5f0d83aadbe682a…
--
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/1abe86fa362eb12ce5f0d83aadbe682a…
You're receiving this email because of your account on dev.gajim.org.
Philipp Hörist pushed to branch master at gajim / gajim
Commits:
b5924afd by lovetox at 2020-03-23T22:54:07+01:00
Interface: Don't try to disconnect on remove_account()
When this method is called we are already disconnected
- - - - -
1 changed file:
- gajim/gui_interface.py
Changes:
=====================================
gajim/gui_interface.py
=====================================
@@ -1780,10 +1780,6 @@ class Interface:
app.app.update_app_actions_state()
def remove_account(self, account):
- if account in app.connections:
- app.connections[account].disconnect(gracefully=True,
- reconnect=False)
-
if app.config.get_per('accounts', account, 'active'):
self.disable_account(account)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/b5924afddfe8cbc1ac68b6274409359c…
--
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/b5924afddfe8cbc1ac68b6274409359c…
You're receiving this email because of your account on dev.gajim.org.