libervia-backend/libervia/frontends/quick_frontend/quick_profile_manager.py
2026-03-31 12:57:17 +02:00

289 lines
9.6 KiB
Python

#!/usr/bin/env python3
# helper class for making a SAT frontend
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from libervia.backend.core.i18n import _
from libervia.backend.core import log as logging
log = logging.getLogger(__name__)
from libervia.tui.constants import Const as C
class ProfileRecord(object):
"""Class which manage data for one profile"""
def __init__(self, profile=None, login=None, password=None):
self._profile = profile
self._login = login
self._password = password
@property
def profile(self):
return self._profile
@profile.setter
def profile(self, value):
self._profile = value
# if we change the profile,
# we must have no login/password until backend give them
self._login = self._password = None
@property
def login(self):
return self._login
@login.setter
def login(self, value):
self._login = value
@property
def password(self):
return self._password
@password.setter
def password(self, value):
self._password = value
class QuickProfileManager(object):
"""Class with manage profiles creation/deletion/connection"""
def __init__(self, host, autoconnect=None):
"""Create the manager
@param host: %(doc_host)s
@param autoconnect(iterable): list of profiles to connect automatically
"""
self.host = host
self._autoconnect = bool(autoconnect)
self.current = ProfileRecord()
def go(self, autoconnect):
if self._autoconnect:
self.autoconnect(autoconnect)
def autoconnect(self, profile_keys):
"""Automatically connect profiles
@param profile_keys(iterable): list of profile keys to connect
"""
if not profile_keys:
log.warning("No profile given to autoconnect")
return
self._autoconnect = True
self._autoconnect_profiles = []
self._do_autoconnect(profile_keys)
def _do_autoconnect(self, profile_keys):
"""Connect automatically given profiles
@param profile_kes(iterable): profiles to connect
"""
assert self._autoconnect
def authenticate_cb(data, cb_id, profile):
if C.bool(data.pop("validated", C.BOOL_FALSE)):
self._autoconnect_profiles.append(profile)
if len(self._autoconnect_profiles) == len(profile_keys):
# all the profiles have been validated
self.host.plug_profiles(self._autoconnect_profiles)
else:
# a profile is not validated, we go to manual mode
self._autoconnect = False
self.host.action_manager(data, callback=authenticate_cb, profile=profile)
def get_profile_name_cb(profile):
if not profile:
# FIXME: this method is not handling manual mode correclty anymore
# must be thought to be handled asynchronously
self._autoconnect = False # manual mode
msg = _("Trying to plug an unknown profile key ({})".format(profile_key))
log.warning(msg)
self.host.show_dialog(_("Profile plugging in error"), msg, "error")
else:
self.host.action_launch(
C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile
)
def get_profile_name_eb(failure):
log.error("Can't retrieve profile name: {}".format(failure))
for profile_key in profile_keys:
self.host.bridge.profile_name_get(
profile_key, callback=get_profile_name_cb, errback=get_profile_name_eb
)
def get_param_error(self, __):
self.host.show_dialog(_("Error"), _("Can't get profile parameter"), "error")
## Helping methods ##
def _get_error_message(self, reason):
"""Return an error message corresponding to profile creation error
@param reason (str): reason as returned by profile_create
@return (unicode): human readable error message
"""
if reason == "ConflictError":
message = _("A profile with this name already exists")
elif reason == "CancelError":
message = _("Profile creation cancelled by backend")
elif reason == "ValueError":
message = _(
"You profile name is not valid"
) # TODO: print a more informative message (empty name, name starting with '@')
else:
message = _("Can't create profile ({})").format(reason)
return message
def _delete_profile(self):
"""Delete the currently selected profile"""
if self.current.profile:
self.host.bridge.profile_delete_async(
self.current.profile, callback=self.refill_profiles
)
self.reset_fields()
## workflow methods (events occuring during the profiles selection) ##
# These methods must be called by the frontend at some point
def _on_connect_profiles(self):
"""Connect the profiles and start the main widget"""
if self._autoconnect:
self.host.show_dialog(
_("Internal error"),
_("You can't connect manually and automatically at the same time"),
"error",
)
return
self.update_connection_params()
profiles = self.get_profiles()
if not profiles:
self.host.show_dialog(
_("No profile selected"),
_("You need to create and select at least one profile before connecting"),
"error",
)
else:
# All profiles in the list are already validated, so we can plug them directly
self.host.plug_profiles(profiles)
def get_connection_params(self, profile):
"""Get login and password and display them
@param profile: %(doc_profile)s
"""
self.host.bridge.param_get_a_async(
"JabberID",
"Connection",
profile_key=profile,
callback=self.set_jid,
errback=self.get_param_error,
)
self.host.bridge.param_get_a_async(
"Password",
"Connection",
profile_key=profile,
callback=self.set_password,
errback=self.get_param_error,
)
def update_connection_params(self):
"""Check if connection parameters have changed, and update them if so"""
if self.current.profile:
login = self.get_jid()
password = self.getPassword()
if login != self.current.login and self.current.login is not None:
self.current.login = login
self.host.bridge.param_set(
"JabberID", login, "Connection", profile_key=self.current.profile
)
log.info("login updated for profile [{}]".format(self.current.profile))
if password != self.current.password and self.current.password is not None:
self.current.password = password
self.host.bridge.param_set(
"Password", password, "Connection", profile_key=self.current.profile
)
log.info("password updated for profile [{}]".format(self.current.profile))
## graphic updates (should probably be overriden in frontends) ##
def reset_fields(self):
"""Set profile to None, and reset fields"""
self.current.profile = None
self.set_jid("")
self.set_password("")
def refill_profiles(self):
"""Rebuild the list of profiles"""
profiles = self.host.bridge.profiles_list_get()
profiles.sort()
self.set_profiles(profiles)
## Method which must be implemented by frontends ##
# get/set data
def get_profiles(self):
"""Return list of selected profiles
Must be implemented by frontends
@return (list): list of profiles
"""
raise NotImplementedError
def set_profiles(self, profiles):
"""Update the list of profiles"""
raise NotImplementedError
def get_jid(self):
"""Get current jid
Must be implemented by frontends
@return (unicode): current jabber id
"""
raise NotImplementedError
def getPassword(self):
"""Get current password
Must be implemented by frontends
@return (unicode): current password
"""
raise NotImplementedError
def set_jid(self, jid_):
"""Set current jid
Must be implemented by frontends
@param jid_(unicode): jabber id to set
"""
raise NotImplementedError
def set_password(self, password):
"""Set current password
Must be implemented by frontends
"""
raise NotImplementedError
# dialogs
# Note: a method which check profiles change must be implemented too