Commit 5a6f03de authored by Philipp Hörist's avatar Philipp Hörist

Add connect_maschine()

Add method that sequentially works the steps we have to do
before sending first presence

- Move Delimiter into own module
- Move Metacontacts into own module
parent db77fa1a
......@@ -415,7 +415,9 @@ def account_is_zeroconf(account):
return connections[account].is_zeroconf
def account_supports_private_storage(account):
return connections[account].private_storage_supported
# If Delimiter module is not available we can assume
# Private Storage is not available
return connections[account].get_module('Delimiter').available
def account_is_connected(account):
if account not in connections:
......
......@@ -115,7 +115,6 @@ class CommonConnection:
# the fake jid
self.groupchat_jids = {} # {ID : groupchat_jid}
self.private_storage_supported = False
self.roster_supported = True
self.addressing_supported = False
......@@ -124,7 +123,8 @@ class CommonConnection:
self.awaiting_cids = {} # Used for XEP-0231
self.nested_group_delimiter = '::'
# Tracks the calls of the connect_maschine() method
self._connect_maschine_calls = 0
self.get_config_values_or_default()
......@@ -412,12 +412,6 @@ class CommonConnection:
def account_changed(self, new_name):
self.name = new_name
def get_metacontacts(self):
"""
To be implemented by derived classes
"""
raise NotImplementedError
def send_agent_status(self, agent, ptype):
"""
To be implemented by derived classes
......@@ -546,8 +540,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.retrycount = 0
self.available_transports = {} # list of available transports on this
# server {'icq': ['icq.server.com', 'icq2.server.com'], }
self.private_storage_supported = True
self.privacy_rules_requested = False
self.streamError = ''
self.secret_hmac = str(random.random())[2:].encode('utf-8')
self.removing_account = False
......@@ -644,7 +637,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.on_purpose = on_purpose
self.connected = 0
self.time_to_reconnect = None
self.get_module('PrivacyLists').supported = False
self.get_module('VCardAvatars').avatar_advertised = False
if on_purpose:
self.sm = Smacks(self)
......@@ -1427,6 +1419,9 @@ class Connection(CommonConnection, ConnectionHandlers):
# Get annotations
self.get_module('Annotations').get_annotations()
# Blocking
self.get_module('Blocking').get_blocking_list()
# Inform GUI we just signed in
app.nec.push_incoming_event(SignedInEvent(None, conn=self))
......@@ -1455,60 +1450,39 @@ class Connection(CommonConnection, ConnectionHandlers):
self.pingalives, self.get_module('Ping').send_keepalive_ping)
self.connection.onreceive(None)
self.privacy_rules_requested = False
# If we are not resuming, we ask for discovery info
# and archiving preferences
if not self.sm.supports_sm or (not self.sm.resuming and self.sm.enabled):
our_server = app.config.get_per('accounts', self.name, 'hostname')
self.get_module('Discovery').discover_account_info()
# This starts the connect_maschine
self.get_module('Discovery').discover_server_info()
else:
self.request_roster(resume=True)
self.get_module('Discovery').discover_account_info()
self.sm.resuming = False # back to previous state
self.sm.resuming = False # back to previous state
# Discover Stun server(s)
if self._proxy is None:
hostname = app.config.get_per('accounts', self.name, 'hostname')
app.resolver.resolve('_stun._udp.' + helpers.idn_to_ascii(hostname),
self._on_stun_resolved)
app.resolver.resolve(
'_stun._udp.' + helpers.idn_to_ascii(hostname),
self._on_stun_resolved)
def _on_stun_resolved(self, host, result_array):
if len(result_array) != 0:
self._stun_servers = self._hosts = [i for i in result_array]
def _continue_connection_request_privacy(self):
if self.get_module('PrivacyLists').supported:
if not self.privacy_rules_requested:
self.privacy_rules_requested = True
self.get_module('PrivacyLists').get_privacy_lists(
self._received_privacy)
else:
# Privacy lists not supported
log.info('Privacy Lists not supported')
self._received_privacy(False)
def _received_privacy(self, result):
if not result:
if (self.continue_connect_info and
self.continue_connect_info[0] == 'invisible'):
# Trying to login as invisible but privacy list not
# supported
self.disconnect(on_purpose=True)
app.nec.push_incoming_event(OurShowEvent(
None, conn=self, show='offline'))
app.nec.push_incoming_event(InformationEvent(
None, dialog_name='invisibility-not-supported',
args=self.name))
return
self.get_module('Blocking').get_blocking_list()
# Ask metacontacts before roster
self.get_metacontacts()
@helpers.call_counter
def connect_maschine(self, restart=False):
log.info('Connect maschine state: %s', self._connect_maschine_calls)
if self._connect_maschine_calls == 1:
self.get_module('MetaContacts').get_metacontacts()
elif self._connect_maschine_calls == 2:
self.get_module('Delimiter').get_roster_delimiter()
elif self._connect_maschine_calls == 3:
self.get_module('Roster').request_roster()
elif self._connect_maschine_calls == 4:
self.send_first_presence()
def send_custom_status(self, show, msg, jid):
if not show in app.SHOW_LIST:
if show not in app.SHOW_LIST:
return -1
if not app.account_is_connected(self.name):
return
......@@ -1677,85 +1651,9 @@ class Connection(CommonConnection, ConnectionHandlers):
query.setTagData('prompt', prompt)
self.connection.SendAndCallForResponse(iq, _on_prompt_result)
def bookmarks_available(self):
if self.private_storage_supported:
return True
if self.get_module('PubSub').publish_options:
return True
return False
def get_roster_delimiter(self):
"""
Get roster group delimiter from storage as described in XEP 0083
"""
if not app.account_is_connected(self.name):
return
iq = nbxmpp.Iq(typ='get')
iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
iq2.addChild(name='roster', namespace='roster:delimiter')
id_ = self.connection.getAnID()
iq.setID(id_)
self.awaiting_answers[id_] = (DELIMITER_ARRIVED, )
self.connection.send(iq)
def set_roster_delimiter(self, delimiter='::'):
"""
Set roster group delimiter to the storage namespace
"""
if not app.account_is_connected(self.name):
return
iq = nbxmpp.Iq(typ='set')
iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
iq3 = iq2.addChild(name='roster', namespace='roster:delimiter')
iq3.setData(delimiter)
self.connection.send(iq)
def get_metacontacts(self):
"""
Get metacontacts list from storage as described in XEP 0049
"""
if not app.account_is_connected(self.name):
return
iq = nbxmpp.Iq(typ='get')
iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
iq2.addChild(name='storage', namespace='storage:metacontacts')
id_ = self.connection.getAnID()
iq.setID(id_)
self.awaiting_answers[id_] = (METACONTACTS_ARRIVED, )
self.connection.send(iq)
def store_metacontacts(self, tags_list):
"""
Send meta contacts to the storage namespace
"""
if not app.account_is_connected(self.name):
return
iq = nbxmpp.Iq(typ='set')
iq2 = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
iq3 = iq2.addChild(name='storage', namespace='storage:metacontacts')
for tag in tags_list:
for data in tags_list[tag]:
jid = data['jid']
dict_ = {'jid': jid, 'tag': tag}
if 'order' in data:
dict_['order'] = data['order']
iq3.addChild(name='meta', attrs=dict_)
self.connection.send(iq)
def getRoster(self):
return self.get_module('Roster')
def request_roster(self, resume=False):
version = None
features = self.connection.Dispatcher.Stream.features
if features and features.getTag('ver', namespace=nbxmpp.NS_ROSTER_VER):
version = app.config.get_per(
'accounts', self.name, 'roster_version')
if not resume:
self.get_module('Roster').request_roster(version)
def send_agent_status(self, agent, ptype):
if not app.account_is_connected(self.name):
return
......
......@@ -53,10 +53,6 @@ log = logging.getLogger('gajim.c.connection_handlers')
# kind of events we can wait for an answer
AGENT_REMOVED = 'agent_removed'
METACONTACTS_ARRIVED = 'metacontacts_arrived'
ROSTER_ARRIVED = 'roster_arrived'
DELIMITER_ARRIVED = 'delimiter_arrived'
PRIVACY_ARRIVED = 'privacy_arrived'
class ConnectionDisco:
......@@ -518,35 +514,6 @@ class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
app.nec.push_incoming_event(AgentRemovedEvent(None, conn=self,
agent=jid))
del self.awaiting_answers[id_]
elif self.awaiting_answers[id_][0] == METACONTACTS_ARRIVED:
if not self.connection:
return
if iq_obj.getType() == 'result':
app.nec.push_incoming_event(MetacontactsReceivedEvent(None,
conn=self, stanza=iq_obj))
else:
if iq_obj.getErrorCode() not in ('403', '406', '404'):
self.private_storage_supported = False
self.get_roster_delimiter()
del self.awaiting_answers[id_]
elif self.awaiting_answers[id_][0] == DELIMITER_ARRIVED:
del self.awaiting_answers[id_]
if not self.connection:
return
if iq_obj.getType() == 'result':
query = iq_obj.getTag('query')
if not query:
return
delimiter = query.getTagData('roster')
if delimiter:
self.nested_group_delimiter = delimiter
else:
self.set_roster_delimiter('::')
else:
self.private_storage_supported = False
# We can now continue connection by requesting the roster
self.request_roster()
def _dispatch_gc_msg_with_captcha(self, stanza, msg_obj):
msg_obj.stanza = stanza
......
......@@ -852,37 +852,6 @@ class UpdateRoomAvatarEvent(nec.NetworkIncomingEvent):
def generate(self):
return True
class MetacontactsReceivedEvent(nec.NetworkIncomingEvent):
name = 'metacontacts-received'
base_network_events = []
def generate(self):
# Metacontact tags
# http://www.xmpp.org/extensions/xep-0209.html
self.meta_list = {}
query = self.stanza.getTag('query')
storage = query.getTag('storage')
metas = storage.getTags('meta')
for meta in metas:
try:
jid = helpers.parse_jid(meta.getAttr('jid'))
except helpers.InvalidFormat:
continue
tag = meta.getAttr('tag')
data = {'jid': jid}
order = meta.getAttr('order')
try:
order = int(order)
except Exception:
order = 0
if order is not None:
data['order'] = order
if tag in self.meta_list:
self.meta_list[tag].append(data)
else:
self.meta_list[tag] = [data]
return True
class ZeroconfNameConflictEvent(nec.NetworkIncomingEvent):
name = 'zeroconf-name-conflict'
base_network_events = []
......
......@@ -732,8 +732,9 @@ class MetacontactManager():
self._metacontacts_tags[brother_account][tag] = [{'jid': brother_jid,
'tag': tag}]
if brother_account != account:
common.app.connections[brother_account].store_metacontacts(
self._metacontacts_tags[brother_account])
con = common.app.connections[brother_account]
con.get_module('MetaContacts').store_metacontacts(
self._metacontacts_tags[brother_account])
# be sure jid has no other tag
old_tag = self._get_metacontacts_tag(account, jid)
while old_tag:
......@@ -748,11 +749,12 @@ class MetacontactManager():
else:
self._metacontacts_tags[account][tag].append({'jid': jid,
'tag': tag})
common.app.connections[account].store_metacontacts(
self._metacontacts_tags[account])
con = common.app.connections[account]
con.get_module('MetaContacts').store_metacontacts(
self._metacontacts_tags[account])
def remove_metacontact(self, account, jid):
if not account in self._metacontacts_tags:
if account not in self._metacontacts_tags:
return
found = None
......@@ -763,8 +765,9 @@ class MetacontactManager():
break
if found:
self._metacontacts_tags[account][tag].remove(found)
common.app.connections[account].store_metacontacts(
self._metacontacts_tags[account])
con = common.app.connections[account]
con.get_module('MetaContacts').store_metacontacts(
self._metacontacts_tags[account])
break
def has_brother(self, account, jid, accounts):
......
......@@ -1508,3 +1508,11 @@ def get_emoticon_theme_path(theme):
emoticons_user_path = os.path.join(configpaths.get('MY_EMOTS'), theme)
if os.path.exists(emoticons_user_path):
return emoticons_user_path
def call_counter(func):
def helper(self, restart=False):
if restart:
self._connect_maschine_calls = 0
self._connect_maschine_calls += 1
return func(self, restart=False)
return helper
......@@ -43,7 +43,7 @@ class ModuleMock:
def __init__(self, name):
self._name = name
# HTTPUpload
# HTTPUpload, ..
self.available = False
# Blocking
......@@ -54,6 +54,9 @@ class ModuleMock:
self.blocked_groups = []
self.blocked_all = False
# Delimiter
self.delimiter = '::'
# Bookmarks
self.bookmarks = {}
......
......@@ -31,6 +31,7 @@ class Bookmarks:
self._con = con
self._account = con.name
self.bookmarks = {}
self.available = False
self.handlers = []
......@@ -65,6 +66,7 @@ class Bookmarks:
self._request_private_bookmarks()
return
self.available = True
log.info('Received Bookmarks (PubSub)')
self._parse_bookmarks(stanza)
self._request_private_bookmarks()
......@@ -84,6 +86,7 @@ class Bookmarks:
if not nbxmpp.isResultNode(stanza):
log.info('No private bookmarks: %s', stanza.getError())
else:
self.available = True
log.info('Received Bookmarks (PrivateStorage)')
merged = self._parse_bookmarks(stanza, check_merge=True)
if merged and self._pubsub_support():
......
# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0083: Nested Roster Groups
import logging
import nbxmpp
log = logging.getLogger('gajim.c.m.delimiter')
class Delimiter:
def __init__(self, con):
self._con = con
self._account = con.name
self.available = False
self.delimiter = '::'
self.handlers = []
def get_roster_delimiter(self):
log.info('Request')
node = nbxmpp.Node('storage', attrs={'xmlns': 'roster:delimiter'})
iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVATE, payload=node)
self._con.connection.SendAndCallForResponse(
iq, self._delimiter_received)
def _delimiter_received(self, stanza):
if not nbxmpp.isResultNode(stanza):
log.info('Request error: %s', stanza.getError())
else:
delimiter = stanza.getQuery().getTagData('roster')
self.available = True
log.info('Delimiter received: %s', delimiter)
if delimiter:
self.delimiter = delimiter
else:
self.set_roster_delimiter()
self._con.connect_maschine()
def set_roster_delimiter(self):
log.info('Set delimiter')
iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE)
roster = iq.getQuery().addChild('roster', namespace='roster:delimiter')
roster.setData('::')
self._con.connection.SendAndCallForResponse(
iq, self._set_delimiter_response)
def _set_delimiter_response(self, stanza):
if not nbxmpp.isResultNode(stanza):
log.info('Store error: %s', stanza.getError())
def get_instance(*args, **kwargs):
return Delimiter(*args, **kwargs), 'Delimiter'
......@@ -199,7 +199,7 @@ class Discovery:
if nbxmpp.NS_ADDRESS in features:
self._con.addressing_supported = True
self._con._continue_connection_request_privacy()
self._con.connect_maschine()
def _parse_transports(self, from_, identities, features, data, node):
for identity in identities:
......
# This file is part of Gajim.
#
# Gajim 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; version 3 only.
#
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
# XEP-0209: Metacontacts
import logging
import nbxmpp
from gajim.common import app
from gajim.common import helpers
from gajim.common.nec import NetworkEvent
log = logging.getLogger('gajim.c.m.metacontacts')
class MetaContacts:
def __init__(self, con):
self._con = con
self._account = con.name
self.available = False
self.handlers = []
def get_metacontacts(self):
log.info('Request')
node = nbxmpp.Node('storage', attrs={'xmlns': 'storage:metacontacts'})
iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVATE, payload=node)
self._con.connection.SendAndCallForResponse(
iq, self._metacontacts_received)
def _metacontacts_received(self, stanza):
if not nbxmpp.isResultNode(stanza):
log.info('Request error: %s', stanza.getError())
else:
self.available = True
meta_list = self._parse_metacontacts(stanza)
log.info('Received: %s', meta_list)
app.nec.push_incoming_event(NetworkEvent(
'metacontacts-received', conn=self._con, meta_list=meta_list))
self._con.connect_maschine()
@staticmethod
def _parse_metacontacts(stanza):
meta_list = {}
query = stanza.getQuery()
storage = query.getTag('storage')
metas = storage.getTags('meta')
for meta in metas:
try:
jid = helpers.parse_jid(meta.getAttr('jid'))
except helpers.InvalidFormat:
continue
tag = meta.getAttr('tag')
data = {'jid': jid}
order = meta.getAttr('order')
try:
order = int(order)
except Exception:
order = 0
if order is not None:
data['order'] = order
if tag in meta_list:
meta_list[tag].append(data)
else:
meta_list[tag] = [data]
return meta_list
def store_metacontacts(self, tags_list):
if not app.account_is_connected(self._account):
return
iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE)
meta = iq.getQuery().addChild('storage',
namespace='storage:metacontacts')
for tag in tags_list:
for data in tags_list[tag]:
jid = data['jid']
dict_ = {'jid': jid, 'tag': tag}
if 'order' in data:
dict_['order'] = data['order']
meta.addChild(name='meta', attrs=dict_)
log.info('Store: %s', tags_list)
self._con.connection.SendAndCallForResponse(
iq, self._store_response_received)
def _store_response_received(self, stanza):
if not nbxmpp.isResultNode(stanza):
log.info('Store error: %s', stanza.getError())
def get_instance(*args, **kwargs):
return MetaContacts(*args, **kwargs), 'MetaContacts'
......@@ -24,10 +24,6 @@ from gajim.common.nec import NetworkEvent
log = logging.getLogger('gajim.c.m.roster')
# TODO: Error IQs
# What if roster not supported on server -> error
RosterItem = namedtuple('RosterItem', 'jid data')
......@@ -65,7 +61,13 @@ class Roster:
app.config.set_per(
'accounts', self._account, 'roster_version', '')
def request_roster(self, version):
def request_roster(self):
version = None
features = self._con.connection.Dispatcher.Stream.features
if features and features.getTag('ver', namespace=nbxmpp.NS_ROSTER_VER):
version = app.config.get_per(
'accounts', self._account, 'roster_version')
log.info('Requested from server')
iq = nbxmpp.Iq('get', nbxmpp.NS_ROSTER)
if version is not None:
......@@ -77,27 +79,26 @@ class Roster:
def _roster_received(self, stanza):
if not nbxmpp.isResultNode(stanza):
log.warning('Unable to retrive roster: %s', stanza.getError())
return
log.info('Received Roster')
received_from_server = False
if stanza.getTag('query') is not None:
# clear Roster
self._data = {}
version = self._parse_roster(stanza)
else:
log.info('Received Roster')
received_from_server = False
if stanza.getTag('query') is not None:
# clear Roster
self._data = {}
version = self._parse_roster(stanza)
log.info('New version: %s', version)
app.logger.replace_roster(self._account, version, self._data)
log.info('New version: %s', version)
app.logger.replace_roster(self._account, version, self._data)
received_from_server = True
received_from_server = True
app.nec.push_incoming_event(NetworkEvent(
'roster-received',
conn=self._con,
roster=self._data.copy(),
received_from_server=received_from_server))
app.nec.push_incoming_event(NetworkEvent(
'roster-received',
conn=self._con,
roster=self._data.copy(),
received_from_server=received_from_server))
self._con.send_first_presence()
self._con.connect_maschine()
def _roster_push_received(self, con, stanza):
log.info('Push received')
......
......@@ -183,11 +183,3 @@ connection_handlers.ConnectionJingle):
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(None, **vars(event)))
def store_metacontacts(self, tags):
"""
Fake empty method
"""
# serverside metacontacts are not supported with zeroconf
# (there is no server)
pass
......@@ -592,7 +592,7 @@ class GroupchatControl(ChatControlBase):
# Bookmarks
con = app.connections[self.account]
bookmark_support = con.bookmarks_available()
bookmark_support = con.get_module('Bookmarks').available
bookmarked = self.room_jid in con.get_module('Bookmarks').bookmarks
win.lookup_action('bookmark-' + self.control_id).set_enabled(
online and bookmark_support and not bookmarked)
......
......@@ -111,7 +111,8 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow):
# Set bookmark switch sensitive if server supports bookmarks
acc = self.account_combo.get_active_id()
if not app.connections[acc].private_storage_supported:
con = app.connections[acc]
if not con.get_module('Bookmarks').available:
self.bookmark_switch.set_sensitive(False)
self.autojoin_switch.set_sensitive(False)
......@@ -256,8 +257,6 @@ class JoinGroupchatWindow(Gtk.ApplicationWindow):
def _add_bookmark(self, account, nickname, password):
con = app.connections[account]
if not con.private_storage_supported:
return