libervia-backend/libervia/backend/bridge/embedded.py
Goffi e241075a34 core: Unseen message counter:
This patches add handling for unseen messages counter, and last message received. The
counters are retrieved from DB on startup, they updated in-memory. Frontend are expected
to have their own in-memory counter too, the backend one is used for re-sync (e.g.; on page
reload).

Bridge methods are added to retrieve the state, and indicate which message has been seen
last by user. A signal is sent on counter reset to all frontends can synchronise.

This is currently local only, but it should be compatible with a future XEP-0490 (Message
Displayed Synchronization) implementation.
2026-06-20 15:11:06 +02:00

1538 lines
46 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# Libervia: an XMPP client
# 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.log import getLogger
log = getLogger(__name__)
from libervia.backend.core import exceptions
from libervia.frontends.bridge.bridge_frontend import BridgeException
import asyncio
from twisted.internet import defer
from twisted.internet.error import ReactorAlreadyInstalledError
class _Bridge:
def __init__(self):
log.debug("Init embedded bridge…")
self._methods_cbs = {}
self._signals_cbs = {"core": {}, "plugin": {}}
def bridge_connect(self, callback, errback):
callback()
def register_method(self, name, callback):
log.debug("registering embedded bridge method [{}]".format(name))
if name in self._methods_cbs:
raise exceptions.ConflictError("method {} is already regitered".format(name))
self._methods_cbs[name] = callback
def register_signal(self, functionName, handler, iface="core"):
iface_dict = self._signals_cbs[iface]
if functionName in iface_dict:
raise exceptions.ConflictError(
"signal {name} is already regitered for interface {iface}".format(
name=functionName, iface=iface
)
)
iface_dict[functionName] = handler
def call_method(self, name, out_sign, async_, args, kwargs):
callback = kwargs.pop("callback", None)
errback = kwargs.pop("errback", None)
if async_:
d = self._methods_cbs[name](*args, **kwargs)
if callback is not None:
d.addCallback(callback if out_sign else lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
else:
try:
ret = self._methods_cbs[name](*args, **kwargs)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
if out_sign:
callback(ret)
else:
callback()
def send_signal(self, name, args, kwargs):
try:
cb = self._signals_cbs["plugin"][name]
except KeyError:
log.debug("ignoring signal {}: no callback registered".format(name))
else:
cb(*args, **kwargs)
def add_method(
self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={}
):
# FIXME: doc parameter is kept only temporary, the time to remove it from calls
log.debug("Adding method [{}] to embedded bridge".format(name))
self.register_method(name, method)
setattr(
self.__class__,
name,
lambda self_, *args, **kwargs: self.call_method(
name, out_sign, async_, args, kwargs
),
)
def add_signal(self, name, int_suffix, signature, doc={}):
setattr(
self.__class__,
name,
lambda self_, *args, **kwargs: self.send_signal(name, args, kwargs),
)
## signals ##
def _debug(self, action, params, profile):
try:
cb = self._signals_cbs["core"]["_debug"]
except KeyError:
log.warning("ignoring signal _debug: no callback registered")
else:
cb(action, params, profile)
def action_new(self, action_data, id, security_limit, profile):
try:
cb = self._signals_cbs["core"]["action_new"]
except KeyError:
log.warning("ignoring signal action_new: no callback registered")
else:
cb(action_data, id, security_limit, profile)
def connected(self, jid_s, profile):
try:
cb = self._signals_cbs["core"]["connected"]
except KeyError:
log.warning("ignoring signal connected: no callback registered")
else:
cb(jid_s, profile)
def contact_deleted(self, entity_jid, profile):
try:
cb = self._signals_cbs["core"]["contact_deleted"]
except KeyError:
log.warning("ignoring signal contact_deleted: no callback registered")
else:
cb(entity_jid, profile)
def contact_new(self, contact_jid, attributes, groups, profile):
try:
cb = self._signals_cbs["core"]["contact_new"]
except KeyError:
log.warning("ignoring signal contact_new: no callback registered")
else:
cb(contact_jid, attributes, groups, profile)
def disconnected(self, profile):
try:
cb = self._signals_cbs["core"]["disconnected"]
except KeyError:
log.warning("ignoring signal disconnected: no callback registered")
else:
cb(profile)
def entity_data_updated(self, jid, name, value, profile):
try:
cb = self._signals_cbs["core"]["entity_data_updated"]
except KeyError:
log.warning("ignoring signal entity_data_updated: no callback registered")
else:
cb(jid, name, value, profile)
def message_encryption_started(self, to_jid, encryption_data, profile_key):
try:
cb = self._signals_cbs["core"]["message_encryption_started"]
except KeyError:
log.warning(
"ignoring signal message_encryption_started: no callback registered"
)
else:
cb(to_jid, encryption_data, profile_key)
def message_encryption_stopped(self, to_jid, encryption_data, profile_key):
try:
cb = self._signals_cbs["core"]["message_encryption_stopped"]
except KeyError:
log.warning(
"ignoring signal message_encryption_stopped: no callback registered"
)
else:
cb(to_jid, encryption_data, profile_key)
def message_new(
self,
uid,
timestamp,
from_jid,
to_jid,
message,
subject,
mess_type,
extra,
profile,
):
try:
cb = self._signals_cbs["core"]["message_new"]
except KeyError:
log.warning("ignoring signal message_new: no callback registered")
else:
cb(
uid,
timestamp,
from_jid,
to_jid,
message,
subject,
mess_type,
extra,
profile,
)
def message_update(self, uid, message_type, message_data, profile):
try:
cb = self._signals_cbs["core"]["message_update"]
except KeyError:
log.warning("ignoring signal message_update: no callback registered")
else:
cb(uid, message_type, message_data, profile)
def messages_display_state_reset(self, peer_jid, profile):
try:
cb = self._signals_cbs["core"]["messages_display_state_reset"]
except KeyError:
log.warning(
"ignoring signal messages_display_state_reset: no callback registered"
)
else:
cb(peer_jid, profile)
def messages_new(self, messages, profile):
try:
cb = self._signals_cbs["core"]["messages_new"]
except KeyError:
log.warning("ignoring signal messages_new: no callback registered")
else:
cb(messages, profile)
def notification_deleted(self, id, profile):
try:
cb = self._signals_cbs["core"]["notification_deleted"]
except KeyError:
log.warning("ignoring signal notification_deleted: no callback registered")
else:
cb(id, profile)
def notification_new(
self,
id,
timestamp,
type,
body_plain,
body_rich,
title,
requires_action,
priority,
expire_at,
extra,
profile,
):
try:
cb = self._signals_cbs["core"]["notification_new"]
except KeyError:
log.warning("ignoring signal notification_new: no callback registered")
else:
cb(
id,
timestamp,
type,
body_plain,
body_rich,
title,
requires_action,
priority,
expire_at,
extra,
profile,
)
def param_update(self, name, value, category, profile):
try:
cb = self._signals_cbs["core"]["param_update"]
except KeyError:
log.warning("ignoring signal param_update: no callback registered")
else:
cb(name, value, category, profile)
def presence_update(self, entity_jid, show, priority, statuses, profile):
try:
cb = self._signals_cbs["core"]["presence_update"]
except KeyError:
log.warning("ignoring signal presence_update: no callback registered")
else:
cb(entity_jid, show, priority, statuses, profile)
def profile_connection_error(self, error, profile):
try:
cb = self._signals_cbs["core"]["profile_connection_error"]
except KeyError:
log.warning(
"ignoring signal profile_connection_error: no callback registered"
)
else:
cb(error, profile)
def profile_ready(self, profile):
try:
cb = self._signals_cbs["core"]["profile_ready"]
except KeyError:
log.warning("ignoring signal profile_ready: no callback registered")
else:
cb(profile)
def progress_error(self, id, error, profile):
try:
cb = self._signals_cbs["core"]["progress_error"]
except KeyError:
log.warning("ignoring signal progress_error: no callback registered")
else:
cb(id, error, profile)
def progress_finished(self, id, metadata, profile):
try:
cb = self._signals_cbs["core"]["progress_finished"]
except KeyError:
log.warning("ignoring signal progress_finished: no callback registered")
else:
cb(id, metadata, profile)
def progress_started(self, id, metadata, profile):
try:
cb = self._signals_cbs["core"]["progress_started"]
except KeyError:
log.warning("ignoring signal progress_started: no callback registered")
else:
cb(id, metadata, profile)
def subscribe(self, sub_type, entity_jid, profile):
try:
cb = self._signals_cbs["core"]["subscribe"]
except KeyError:
log.warning("ignoring signal subscribe: no callback registered")
else:
cb(sub_type, entity_jid, profile)
## methods ##
def action_launch(
self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None
):
d = self._methods_cbs["action_launch"](callback_id, data, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def actions_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
try:
ret = self._methods_cbs["actions_get"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def config_get(self, section, name, callback=None, errback=None):
try:
ret = self._methods_cbs["config_get"](section, name)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def connect(
self,
profile_key="@DEFAULT@",
password="",
options={},
callback=None,
errback=None,
):
d = self._methods_cbs["connect"](profile_key, password, options)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def contact_add(
self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None
):
try:
ret = self._methods_cbs["contact_add"](entity_jid, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def contact_del(
self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None
):
d = self._methods_cbs["contact_del"](entity_jid, profile_key)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def contact_get(self, arg_0, profile_key="@DEFAULT@", callback=None, errback=None):
d = self._methods_cbs["contact_get"](arg_0, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def contact_update(
self,
entity_jid,
name,
groups,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
try:
ret = self._methods_cbs["contact_update"](
entity_jid, name, groups, profile_key
)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def contacts_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
d = self._methods_cbs["contacts_get"](profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def contacts_get_from_group(
self, group, profile_key="@DEFAULT@", callback=None, errback=None
):
try:
ret = self._methods_cbs["contacts_get_from_group"](group, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def devices_infos_get(self, bare_jid, profile_key, callback=None, errback=None):
d = self._methods_cbs["devices_infos_get"](bare_jid, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def disco_find_by_features(
self,
namespaces,
identities,
bare_jid=False,
service=True,
roster=True,
own_jid=True,
local_device=False,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["disco_find_by_features"](
namespaces,
identities,
bare_jid,
service,
roster,
own_jid,
local_device,
profile_key,
)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def disco_infos(
self,
entity_jid,
node="",
use_cache=True,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["disco_infos"](entity_jid, node, use_cache, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def disco_items(
self,
entity_jid,
node="",
use_cache=True,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["disco_items"](entity_jid, node, use_cache, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None):
d = self._methods_cbs["disconnect"](profile_key)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def encryption_namespace_get(self, arg_0, callback=None, errback=None):
try:
ret = self._methods_cbs["encryption_namespace_get"](arg_0)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def encryption_plugins_get(self, callback=None, errback=None):
try:
ret = self._methods_cbs["encryption_plugins_get"]()
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def encryption_trust_ui_get(
self, to_jid, namespace, profile_key, callback=None, errback=None
):
d = self._methods_cbs["encryption_trust_ui_get"](to_jid, namespace, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def entities_data_get(self, jids, keys, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["entities_data_get"](jids, keys, profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def entity_data_get(self, jid, keys, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["entity_data_get"](jid, keys, profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def features_get(self, profile_key, callback=None, errback=None):
d = self._methods_cbs["features_get"](profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def history_get(
self,
from_jid,
to_jid,
limit,
between=True,
filters="",
profile="@NONE@",
callback=None,
errback=None,
):
d = self._methods_cbs["history_get"](
from_jid, to_jid, limit, between, filters, profile
)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def history_last_messages_metadata_get(
self, options, profile_key="@NONE@", callback=None, errback=None
):
d = self._methods_cbs["history_last_messages_metadata_get"](options, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def image_check(self, arg_0, callback=None, errback=None):
try:
ret = self._methods_cbs["image_check"](arg_0)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def image_convert(self, source, dest, arg_2, extra, callback=None, errback=None):
d = self._methods_cbs["image_convert"](source, dest, arg_2, extra)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def image_generate_preview(
self, image_path, profile_key, callback=None, errback=None
):
d = self._methods_cbs["image_generate_preview"](image_path, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def image_resize(self, image_path, width, height, callback=None, errback=None):
d = self._methods_cbs["image_resize"](image_path, width, height)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def init_pre_script(self, callback=None, errback=None):
d = self._methods_cbs["init_pre_script"]()
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def is_connected(self, profile_key="@DEFAULT@", callback=None, errback=None):
try:
ret = self._methods_cbs["is_connected"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def main_resource_get(
self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None
):
try:
ret = self._methods_cbs["main_resource_get"](contact_jid, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def menu_help_get(self, menu_id, language, callback=None, errback=None):
try:
ret = self._methods_cbs["menu_help_get"](menu_id, language)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def menu_launch(
self,
menu_type,
path,
data,
security_limit,
profile_key,
callback=None,
errback=None,
):
d = self._methods_cbs["menu_launch"](
menu_type, path, data, security_limit, profile_key
)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def menus_get(self, language, security_limit, callback=None, errback=None):
try:
ret = self._methods_cbs["menus_get"](language, security_limit)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def message_encryption_get(self, to_jid, profile_key, callback=None, errback=None):
try:
ret = self._methods_cbs["message_encryption_get"](to_jid, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def message_encryption_start(
self,
to_jid,
namespace="",
replace=False,
profile_key="@NONE@",
callback=None,
errback=None,
):
d = self._methods_cbs["message_encryption_start"](
to_jid, namespace, replace, profile_key
)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def message_encryption_stop(self, to_jid, profile_key, callback=None, errback=None):
d = self._methods_cbs["message_encryption_stop"](to_jid, profile_key)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def message_send(
self,
to_jid,
message,
subject={},
mess_type="auto",
extra={},
profile_key="@NONE@",
callback=None,
errback=None,
):
d = self._methods_cbs["message_send"](
to_jid, message, subject, mess_type, extra, profile_key
)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def messages_display_state_get(self, jids, profile_key, callback=None, errback=None):
try:
ret = self._methods_cbs["messages_display_state_get"](jids, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def messages_display_state_set(
self, peer_jid, message_uid, profile_key, callback=None, errback=None
):
try:
ret = self._methods_cbs["messages_display_state_set"](
peer_jid, message_uid, profile_key
)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def namespaces_get(self, callback=None, errback=None):
try:
ret = self._methods_cbs["namespaces_get"]()
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def notification_add(
self,
type_,
body_plain,
body_rich,
title,
is_global,
requires_action,
arg_6,
priority,
expire_at,
extra,
callback=None,
errback=None,
):
try:
ret = self._methods_cbs["notification_add"](
type_,
body_plain,
body_rich,
title,
is_global,
requires_action,
arg_6,
priority,
expire_at,
extra,
)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def notification_delete(
self, id_, is_global, profile_key, callback=None, errback=None
):
try:
ret = self._methods_cbs["notification_delete"](id_, is_global, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def notifications_expired_clean(
self, limit_timestamp, profile_key, callback=None, errback=None
):
try:
ret = self._methods_cbs["notifications_expired_clean"](
limit_timestamp, profile_key
)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def notifications_get(self, filters, profile_key, callback=None, errback=None):
try:
ret = self._methods_cbs["notifications_get"](filters, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def param_get_a(
self,
name,
category,
attribute="value",
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
try:
ret = self._methods_cbs["param_get_a"](name, category, attribute, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def param_get_a_async(
self,
name,
category,
attribute="value",
security_limit=-1,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["param_get_a_async"](
name, category, attribute, security_limit, profile_key
)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def param_set(
self,
name,
value,
category,
security_limit=-1,
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
try:
ret = self._methods_cbs["param_set"](
name, value, category, security_limit, profile_key
)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def param_ui_get(
self,
security_limit=-1,
app="",
extra="",
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["param_ui_get"](security_limit, app, extra, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def params_categories_get(self, callback=None, errback=None):
try:
ret = self._methods_cbs["params_categories_get"]()
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def params_register_app(
self, xml, security_limit=-1, app="", callback=None, errback=None
):
try:
ret = self._methods_cbs["params_register_app"](xml, security_limit, app)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def params_template_load(self, filename, callback=None, errback=None):
try:
ret = self._methods_cbs["params_template_load"](filename)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def params_template_save(self, filename, callback=None, errback=None):
try:
ret = self._methods_cbs["params_template_save"](filename)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def params_values_from_category_get_async(
self,
category,
security_limit=-1,
app="",
extra="",
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
d = self._methods_cbs["params_values_from_category_get_async"](
category, security_limit, app, extra, profile_key
)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def presence_set(
self,
to_jid="",
show="",
statuses={},
profile_key="@DEFAULT@",
callback=None,
errback=None,
):
try:
ret = self._methods_cbs["presence_set"](to_jid, show, statuses, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def presence_statuses_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
try:
ret = self._methods_cbs["presence_statuses_get"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def private_data_delete(self, namespace, key, arg_2, callback=None, errback=None):
d = self._methods_cbs["private_data_delete"](namespace, key, arg_2)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def private_data_get(self, namespace, key, profile_key, callback=None, errback=None):
d = self._methods_cbs["private_data_get"](namespace, key, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def private_data_set(
self, namespace, key, data, profile_key, callback=None, errback=None
):
d = self._methods_cbs["private_data_set"](namespace, key, data, profile_key)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def profile_create(
self, profile, password="", component="", callback=None, errback=None
):
d = self._methods_cbs["profile_create"](profile, password, component)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def profile_delete_async(self, profile, callback=None, errback=None):
d = self._methods_cbs["profile_delete_async"](profile)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def profile_is_session_started(
self, profile_key="@DEFAULT@", callback=None, errback=None
):
try:
ret = self._methods_cbs["profile_is_session_started"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def profile_name_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
try:
ret = self._methods_cbs["profile_name_get"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def profile_set_default(self, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["profile_set_default"](profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def profile_start_session(
self, password="", profile_key="@DEFAULT@", callback=None, errback=None
):
d = self._methods_cbs["profile_start_session"](password, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def profiles_list_get(
self, clients=True, components=False, callback=None, errback=None
):
try:
ret = self._methods_cbs["profiles_list_get"](clients, components)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def progress_get(self, id, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["progress_get"](id, profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def progress_get_all(self, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["progress_get_all"](profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def progress_get_all_metadata(self, profile, callback=None, errback=None):
try:
ret = self._methods_cbs["progress_get_all_metadata"](profile)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def ready_get(self, callback=None, errback=None):
d = self._methods_cbs["ready_get"]()
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def roster_resync(self, profile_key="@DEFAULT@", callback=None, errback=None):
d = self._methods_cbs["roster_resync"](profile_key)
if callback is not None:
d.addCallback(lambda __: callback())
if errback is not None:
d.addErrback(errback)
return d
def session_infos_get(self, profile_key, callback=None, errback=None):
d = self._methods_cbs["session_infos_get"](profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def sub_waiting_get(self, profile_key="@DEFAULT@", callback=None, errback=None):
try:
ret = self._methods_cbs["sub_waiting_get"](profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
def subscription(
self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None
):
try:
ret = self._methods_cbs["subscription"](sub_type, entity, profile_key)
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback()
def tor_infos_get(self, extra, profile_key, callback=None, errback=None):
d = self._methods_cbs["tor_infos_get"](extra, profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def tor_mode_get(self, profile_key, callback=None, errback=None):
d = self._methods_cbs["tor_mode_get"](profile_key)
if callback is not None:
d.addCallback(callback)
if errback is not None:
d.addErrback(errback)
return d
def version_get(self, callback=None, errback=None):
try:
ret = self._methods_cbs["version_get"]()
except Exception as e:
if errback is not None:
errback(e)
else:
raise e
else:
if callback is None:
return ret
else:
callback(ret)
# Singleton: shared instance between backend and frontend
_bridge_instance = None
def bridge():
"""Return the singleton _Bridge instance, creating it if necessary."""
global _bridge_instance
if _bridge_instance is None:
_bridge_instance = _Bridge()
return _bridge_instance
class TwistedBridge:
"""Embedded bridge wrapper that returns Twisted Deferreds.
Starts ``LiberviaBackend`` on first bridge connection.
"""
def __init__(self):
self._connected = False
async def post_init(self):
pass
def _ensure_reactor(self):
"""Ensure asyncioreactor is installed."""
from twisted.internet import asyncioreactor
# Python 3.14+ workaround: ensure an event loop exists before installing reactor
try:
asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
asyncioreactor.install()
except ReactorAlreadyInstalledError:
from twisted.internet import reactor
if not isinstance(reactor, asyncioreactor.AsyncioSelectorReactor):
raise exceptions.InternalError(
f"Wrong reactor installed: {reactor}"
) from None
def _start_backend(self):
"""Start LiberviaBackend and return its initialised Deferred."""
from libervia.backend.core.main import LiberviaBackend
self._ensure_reactor()
backend = LiberviaBackend()
backend._init()
return backend.initialised
def bridge_connect(self):
"""Connect to the embedded backend, starting it if necessary."""
if self._connected:
return defer.succeed(None)
self._connected = True
return self._start_backend()
def register_signal(self, name, handler, iface="core"):
"""Register a signal handler on the embedded bridge."""
bridge().register_signal(name, handler, iface)
def _errback(self, failure_):
"""Convert Failure to BridgeException."""
e = failure_.value
if isinstance(e, BridgeException):
raise e
try:
condition = e.condition
except AttributeError:
condition = ""
raise BridgeException(
type(e).__module__ + "." + type(e).__name__, str(e), condition
) from e
def __getattr__(self, name):
"""Delegate method calls, ensuring they return Deferreds."""
attr = getattr(bridge(), name)
if callable(attr) and not name.startswith("_"):
def deferred_wrapper(*args, **kwargs):
result = attr(*args, **kwargs)
if isinstance(result, defer.Deferred):
result.addErrback(self._errback)
return result
return defer.succeed(result)
return deferred_wrapper
return attr
class AIOBridge(TwistedBridge):
"""Embedded bridge wrapper that returns asyncio Futures."""
def bridge_connect(self):
"""Connect to the embedded backend, starting it if necessary."""
if self._connected:
return asyncio.ensure_future(defer.succeed(None))
self._connected = True
d = self._start_backend()
return d.asFuture(asyncio.get_event_loop())
def __getattr__(self, name):
"""Delegate method calls, converting Deferreds to Futures."""
attr = super().__getattr__(name)
if callable(attr) and not name.startswith("_"):
def aio_wrapper(*args, **kwargs):
return attr(*args, **kwargs).asFuture(asyncio.get_event_loop())
return aio_wrapper
return attr