libervia-backend/sat/bridge/pb.py
Goffi 5cee4fbab7 bridge (pb): implemented an asyncio compatible bridge:
`pb` bridge can now be used with asyncio by instantiating AIOBridge.
2019-09-25 08:53:38 +02:00

192 lines
7 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
# -*- coding: utf-8 -*-
# SAT: a jabber client
# Copyright (C) 2009-2019 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/>.
import dataclasses
from sat.core.log import getLogger
from twisted.spread import jelly, pb
from twisted.internet import reactor
log = getLogger(__name__)
## jelly hack
# we monkey patch jelly to handle namedtuple
ori_jelly = jelly._Jellier.jelly
def fixed_jelly(self, obj):
"""this method fix handling of namedtuple"""
if isinstance(obj, tuple) and not obj is tuple:
obj = tuple(obj)
return ori_jelly(self, obj)
jelly._Jellier.jelly = fixed_jelly
@dataclasses.dataclass(eq=False)
class HandlerWrapper:
# we use a wrapper to keep signals handlers because RemoteReference doesn't support
# comparison (other than equality), making it unusable with a list
handler: pb.RemoteReference
class PBRoot(pb.Root):
def __init__(self):
self.signals_handlers = []
def remote_initBridge(self, signals_handler):
self.signals_handlers.append(HandlerWrapper(signals_handler))
log.info("registered signal handler")
def sendSignalEb(self, failure_, signal_name):
if not failure_.check(pb.PBConnectionLost):
log.error(
f"Error while sending signal {signal_name}: {failure_}",
)
def sendSignal(self, name, args, kwargs):
to_remove = []
for wrapper in self.signals_handlers:
handler = wrapper.handler
try:
d = handler.callRemote(name, *args, **kwargs)
except pb.DeadReferenceError:
to_remove.append(wrapper)
else:
d.addErrback(self.sendSignalEb, name)
if to_remove:
for wrapper in to_remove:
log.debug("Removing signal handler for dead frontend")
self.signals_handlers.remove(wrapper)
def _bridgeDeactivateSignals(self):
if hasattr(self, "signals_paused"):
log.warning("bridge signals already deactivated")
if self.signals_handler:
self.signals_paused.extend(self.signals_handler)
else:
self.signals_paused = self.signals_handlers
self.signals_handlers = []
log.debug("bridge signals have been deactivated")
def _bridgeReactivateSignals(self):
try:
self.signals_handlers = self.signals_paused
except AttributeError:
log.debug("signals were already activated")
else:
del self.signals_paused
log.debug("bridge signals have been reactivated")
##METHODS_PART##
class Bridge(object):
def __init__(self):
log.info("Init Perspective Broker...")
self.root = PBRoot()
reactor.listenTCP(8789, pb.PBServerFactory(self.root))
def sendSignal(self, name, *args, **kwargs):
self.root.sendSignal(name, args, kwargs)
def remote_initBridge(self, signals_handler):
self.signals_handlers.append(signals_handler)
log.info("registered signal handler")
def register_method(self, name, callback):
log.debug("registering PB bridge method [%s]" % name)
setattr(self.root, "remote_" + name, callback)
#  self.root.register_method(name, callback)
def addMethod(self, name, int_suffix, in_sign, out_sign, method, async_=False, doc={}):
"""Dynamically add a method to PB Bridge"""
# FIXME: doc parameter is kept only temporary, the time to remove it from calls
log.debug("Adding method {name} to PB bridge".format(name=name))
self.register_method(name, method)
def addSignal(self, name, int_suffix, signature, doc={}):
log.debug("Adding signal {name} to PB bridge".format(name=name))
setattr(
self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs)
)
def bridgeDeactivateSignals(self):
"""Stop sending signals to bridge
Mainly used for mobile frontends, when the frontend is paused
"""
self.root._bridgeDeactivateSignals()
def bridgeReactivateSignals(self):
"""Send again signals to bridge
Should only be used after bridgeDeactivateSignals has been called
"""
self.root._bridgeReactivateSignals()
def _debug(self, action, params, profile):
self.sendSignal("_debug", action, params, profile)
def actionNew(self, action_data, id, security_limit, profile):
self.sendSignal("actionNew", action_data, id, security_limit, profile)
def connected(self, jid_s, profile):
self.sendSignal("connected", jid_s, profile)
def contactDeleted(self, entity_jid, profile):
self.sendSignal("contactDeleted", entity_jid, profile)
def disconnected(self, profile):
self.sendSignal("disconnected", profile)
def entityDataUpdated(self, jid, name, value, profile):
self.sendSignal("entityDataUpdated", jid, name, value, profile)
def messageEncryptionStarted(self, to_jid, encryption_data, profile_key):
self.sendSignal("messageEncryptionStarted", to_jid, encryption_data, profile_key)
def messageEncryptionStopped(self, to_jid, encryption_data, profile_key):
self.sendSignal("messageEncryptionStopped", to_jid, encryption_data, profile_key)
def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile):
self.sendSignal("messageNew", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile)
def newContact(self, contact_jid, attributes, groups, profile):
self.sendSignal("newContact", contact_jid, attributes, groups, profile)
def paramUpdate(self, name, value, category, profile):
self.sendSignal("paramUpdate", name, value, category, profile)
def presenceUpdate(self, entity_jid, show, priority, statuses, profile):
self.sendSignal("presenceUpdate", entity_jid, show, priority, statuses, profile)
def progressError(self, id, error, profile):
self.sendSignal("progressError", id, error, profile)
def progressFinished(self, id, metadata, profile):
self.sendSignal("progressFinished", id, metadata, profile)
def progressStarted(self, id, metadata, profile):
self.sendSignal("progressStarted", id, metadata, profile)
def subscribe(self, sub_type, entity_jid, profile):
self.sendSignal("subscribe", sub_type, entity_jid, profile)