diff --git a/sat/core/sat_main.py b/sat/core/sat_main.py index fe7aeee7..ba663177 100644 --- a/sat/core/sat_main.py +++ b/sat/core/sat_main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # SAT: a jabber client # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) @@ -22,7 +21,7 @@ import sys import os.path import uuid import sat -from sat.core.i18n import _, languageSwitch +from sat.core.i18n import _, D_, languageSwitch from sat.core import patches patches.apply() from twisted.application import service @@ -172,7 +171,7 @@ class SAT(service.Service): self.bridge.register_method("imageCheck", self._imageCheck) self.bridge.register_method("imageResize", self._imageResize) - self.memory.initialized.addCallback(self._postMemoryInit) + self.memory.initialized.addCallback(lambda __: defer.ensureDeferred(self._postMemoryInit())) @property def version(self): @@ -202,7 +201,7 @@ class SAT(service.Service): def bridge_name(self): return os.path.splitext(os.path.basename(self.bridge.__file__))[0] - def _postMemoryInit(self, ignore): + async def _postMemoryInit(self): """Method called after memory initialization is done""" self.common_cache = cache.Cache(self, None) log.info(_("Memory initialised")) @@ -218,9 +217,37 @@ class SAT(service.Service): ) sys.exit(1) self._addBaseMenus() + self.initialised.callback(None) log.info(_("Backend is ready")) + # profile autoconnection must be done after self.initialised is called because + # startSession waits for it. + autoconnect_dict = await self.memory.storage.getIndParamValues( + category='Connection', name='autoconnect_backend', + ) + profiles_autoconnect = [p for p, v in autoconnect_dict.items() if C.bool(v)] + if not self.trigger.point("profilesAutoconnect", profiles_autoconnect): + return + if profiles_autoconnect: + log.info(D_( + "Following profiles will be connected automatically: {profiles}" + ).format(profiles= ', '.join(profiles_autoconnect))) + connect_d_list = [] + for profile in profiles_autoconnect: + connect_d_list.append(defer.ensureDeferred(self.connect(profile))) + + if connect_d_list: + results = await defer.DeferredList(connect_d_list) + for idx, (success, result) in enumerate(results): + if not success: + profile = profiles_autoconnect[0] + log.warning( + _("Can't autoconnect profile {profile}: {reason}").format( + profile = profile, + reason = result) + ) + def _addBaseMenus(self): """Add base menus""" encryption.EncryptionHandler._importMenus(self) @@ -407,9 +434,10 @@ class SAT(service.Service): def _connect(self, profile_key, password="", options=None): profile = self.memory.getProfileName(profile_key) - return self.connect(profile, password, options) + return defer.ensureDeferred(self.connect(profile, password, options)) - def connect(self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES): + async def connect( + self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES): """Connect a profile (i.e. connect client.component to XMPP server) Retrieve the individual parameters, authenticate the profile @@ -427,20 +455,18 @@ class SAT(service.Service): if options is None: options = {} - def connectProfile(__=None): - if self.isConnected(profile): - log.info(_("already connected !")) - return True + await self.memory.startSession(password, profile) - if self.memory.isComponent(profile): - d = xmpp.SatXMPPComponent.startConnection(self, profile, max_retries) - else: - d = xmpp.SatXMPPClient.startConnection(self, profile, max_retries) - return d.addCallback(lambda __: False) + if self.isConnected(profile): + log.info(_("already connected !")) + return True - d = self.memory.startSession(password, profile) - d.addCallback(connectProfile) - return d + if self.memory.isComponent(profile): + await xmpp.SatXMPPComponent.startConnection(self, profile, max_retries) + else: + await xmpp.SatXMPPClient.startConnection(self, profile, max_retries) + + return False def disconnect(self, profile_key): """disconnect from jabber server""" diff --git a/sat/memory/params.py b/sat/memory/params.py index 3714e6f4..2160cac9 100644 --- a/sat/memory/params.py +++ b/sat/memory/params.py @@ -74,6 +74,7 @@ class Params(object): + @@ -92,6 +93,7 @@ class Params(object): "force_server_param": C.FORCE_SERVER_PARAM, "force_port_param": C.FORCE_PORT_PARAM, "new_account_label": D_("Register new account"), + "autoconnect_backend_label": D_("Connect on backend startup"), "autoconnect_label": D_("Connect on frontend startup"), "autodisconnect_label": D_("Disconnect on frontend closure"), "check_certificate_label": D_("Check certificate (don't uncheck if unsure)"), diff --git a/sat/memory/sqlite.py b/sat/memory/sqlite.py index eb792a1d..176f99c4 100644 --- a/sat/memory/sqlite.py +++ b/sat/memory/sqlite.py @@ -196,8 +196,10 @@ class SqliteStorage(object): @param db_filename: full path to the Sqlite database """ - self.initialized = defer.Deferred() # triggered when memory is fully initialised and ready - self.profiles = {} # we keep cache for the profiles (key: profile name, value: profile id) + # triggered when memory is fully initialised and ready + self.initialized = defer.Deferred() + # we keep cache for the profiles (key: profile name, value: profile id) + self.profiles = {} log.info(_("Connecting database")) new_base = not os.path.exists(db_filename) # do we have to create the database ? @@ -231,10 +233,10 @@ class SqliteStorage(object): init_defer.addCallback(self.commitStatements) def fillProfileCache(ignore): - d = self.dbpool.runQuery("SELECT profile_id, entry_point FROM components").addCallback(self._cacheComponentsAndProfiles) - d.chainDeferred(self.initialized) + return self.dbpool.runQuery("SELECT profile_id, entry_point FROM components").addCallback(self._cacheComponentsAndProfiles) init_defer.addCallback(fillProfileCache) + init_defer.chainDeferred(self.initialized) def commitStatements(self, statements): @@ -387,10 +389,26 @@ class SqliteStorage(object): @param profile: %(doc_profile)s @return: deferred """ - d = self.dbpool.runQuery("SELECT value FROM param_ind WHERE category=? AND name=? AND profile_id=?", (category, name, self.profiles[profile])) + d = self.dbpool.runQuery( + "SELECT value FROM param_ind WHERE category=? AND name=? AND profile_id=?", + (category, name, self.profiles[profile])) d.addCallback(self.__getFirstResult) return d + async def getIndParamValues(self, category, name): + """Ask database for the individual values of a parameter for all profiles + + @param category: category of the parameter + @param name: name of the parameter + @return dict: profile => value map + """ + result = await self.dbpool.runQuery( + "SELECT profiles.name, param_ind.value FROM param_ind JOIN profiles ON " + "param_ind.profile_id = profiles.id WHERE param_ind.category=? " + "and param_ind.name=?", + (category, name)) + return dict(result) + def setGenParam(self, category, name, value): """Save the general parameters in database diff --git a/sat/plugins/plugin_misc_account.py b/sat/plugins/plugin_misc_account.py index 16dfcecf..eff49d2a 100644 --- a/sat/plugins/plugin_misc_account.py +++ b/sat/plugins/plugin_misc_account.py @@ -20,7 +20,6 @@ from sat.core.i18n import _, D_ from sat.core.log import getLogger -log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools from sat.memory.memory import Sessions @@ -32,6 +31,10 @@ from twisted.python.failure import Failure from twisted.words.protocols.jabber import jid from sat.tools.common import email as sat_email + +log = getLogger(__name__) + + #  FIXME: this plugin code is old and need a cleaning # TODO: account deletion/password change need testing @@ -742,7 +745,8 @@ class MiscAccount(object): d.addCallback( lambda __: self.host.memory.getProfileName(jid_s) ) # checks if the profile has been successfuly created - d.addCallback(self.host.connect, password, {}, 0) + d.addCallback(lambda profile: defer.ensureDeferred( + self.host.connect(profile, password, {}, 0))) def connected(result): self.sendEmails(None, profile=jid_s) diff --git a/sat/plugins/plugin_misc_email_invitation.py b/sat/plugins/plugin_misc_email_invitation.py index 7314b707..cd38d59a 100644 --- a/sat/plugins/plugin_misc_email_invitation.py +++ b/sat/plugins/plugin_misc_email_invitation.py @@ -281,7 +281,7 @@ class InvitationsPlugin(object): except KeyError: pass else: - yield self.host.connect(guest_profile, password) + yield defer.ensureDeferred(self.host.connect(guest_profile, password)) guest_client = self.host.getClient(guest_profile) yield id_plugin.setIdentity(guest_client, {'nick': name}) yield self.host.disconnect(guest_profile) diff --git a/sat/stdui/ui_profile_manager.py b/sat/stdui/ui_profile_manager.py index 7a84c60c..944ea59b 100644 --- a/sat/stdui/ui_profile_manager.py +++ b/sat/stdui/ui_profile_manager.py @@ -21,14 +21,16 @@ from sat.core.i18n import D_ from sat.core.constants import Const as C from sat.core.log import getLogger - -log = getLogger(__name__) from sat.core import exceptions from sat.tools import xml_tools from sat.memory.memory import ProfileSessions +from twisted.internet import defer from twisted.words.protocols.jabber import jid +log = getLogger(__name__) + + class ProfileManager(object): """Manage profiles.""" @@ -143,7 +145,7 @@ class ProfileManager(object): d = self.host.memory.setParam( "Password", xmpp_password, "Connection", profile_key=profile ) - d.addCallback(lambda __: self.host.connect(profile)) + d.addCallback(lambda __: defer.ensureDeferred(self.host.connect(profile))) d.addCallback(lambda __: {}) d.addErrback(lambda __: self._changeXMPPPassword({}, profile)) return d