core: backend autoconnection:

A new Connection/autoconnect_backend param can be set for a profile or component to be
started automatically with backend. This is specially useful for components, but can be
useful for client profile too (e.g. on Android we need to start profile with backend to
get notifications, this part will come with following commits).

The new Sqlite.getIndParamValues method allows to retrieve the same parameters for all
profiles.
This commit is contained in:
Goffi 2020-01-25 21:08:32 +01:00
commit 1d91db6f2c
6 changed files with 81 additions and 29 deletions

View file

@ -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"""

View file

@ -74,6 +74,7 @@ class Params(object):
<param name="Priority" value="50" type="int" constraint="-128;127" security="10" />
<param name="%(force_server_param)s" value="" type="string" security="50" />
<param name="%(force_port_param)s" value="" type="int" constraint="1;65535" security="50" />
<param name="autoconnect_backend" label="%(autoconnect_backend_label)s" value="false" type="bool" security="50" />
<param name="autoconnect" label="%(autoconnect_label)s" value="true" type="bool" security="50" />
<param name="autodisconnect" label="%(autodisconnect_label)s" value="false" type="bool" security="50" />
<param name="check_certificate" label="%(check_certificate_label)s" value="true" type="bool" security="4" />
@ -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)"),

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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