plugin file, XEP-0234: registering is now done by class + use of async:

- instead of registering a callback, a file sending manager now register itself and must
  implement some well known method (`fileSend`, `canHandleFileSend`) and optionally a
  `name` attribute
- `utils.asDeferred` is now used for callbacks, so all type of methods including
  coroutines can be used.
- feature checking is now handled by `canHandleFileSend` method instead of simple
  namespace check, this allows to use a method when namespace can't be checked (this is
  the case when a file is sent to a bare jid with jingle)
This commit is contained in:
Goffi 2020-11-12 14:53:15 +01:00
commit 46c763cbf7

View file

@ -17,18 +17,21 @@
# 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 os
import os.path
from functools import partial
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
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.tools import stream
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
import os
import os.path
from sat.tools import utils
log = getLogger(__name__)
PLUGIN_INFO = {
@ -73,7 +76,7 @@ class FilePlugin:
method=self._fileSend,
async_=True,
)
self._file_callbacks = []
self._file_managers = []
host.importMenu(
(D_("Action"), D_("send file")),
self._fileSendMenu,
@ -85,12 +88,11 @@ class FilePlugin:
def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None,
profile=C.PROF_KEY_NONE):
client = self.host.getClient(profile)
return self.fileSend(
return defer.ensureDeferred(self.fileSend(
client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra
)
))
@defer.inlineCallbacks
def fileSend(
async def fileSend(
self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None
):
"""Send a file using best available method
@ -107,29 +109,45 @@ class FilePlugin:
raise exceptions.DataError("The given path doesn't link to a file")
if not filename:
filename = os.path.basename(filepath) or "_"
for namespace, callback, priority, method_name in self._file_callbacks:
has_feature = yield self.host.hasFeature(client, namespace, peer_jid)
if has_feature:
for manager, priority in self._file_managers:
if await utils.asDeferred(manager.canHandleFileSend,
client, peer_jid, filepath):
try:
method_name = manager.name
except AttributeError:
method_name = manager.__class__.__name__
log.info(
"{name} method will be used to send the file".format(
_("{name} method will be used to send the file").format(
name=method_name
)
)
progress_id = yield callback(
client, peer_jid, filepath, filename, file_desc, extra
)
defer.returnValue({"progress": progress_id})
try:
progress_id = await utils.asDeferred(
manager.fileSend, client, peer_jid, filepath, filename, file_desc,
extra
)
except Exception as e:
log.warning(
_("Can't send {filepath} to {peer_jid} with {method_name}: "
"{reason}").format(
filepath=filepath,
peer_jid=peer_jid,
method_name=method_name,
reason=e
)
)
continue
return {"progress": progress_id}
msg = "Can't find any method to send file to {jid}".format(jid=peer_jid.full())
log.warning(msg)
defer.returnValue(
{
"xmlui": xml_tools.note(
"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING
).toXml()
}
)
return {
"xmlui": xml_tools.note(
"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING
).toXml()
}
def _onFileChoosed(self, client, peer_jid, data):
def _onFileChoosed(self, peer_jid, data, profile):
client = self.host.getClient(profile)
cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE))
if cancelled:
return
@ -147,9 +165,7 @@ class FilePlugin:
raise exceptions.DataError(_("Invalid JID"))
file_choosed_id = self.host.registerCallback(
lambda data, profile: self._onFileChoosed(
self.host.getClient(profile), jid_, data
),
partial(self._onFileChoosed, jid_),
with_data=True,
one_shot=True,
)
@ -165,30 +181,31 @@ class FilePlugin:
return {"xmlui": xml_ui.toXml()}
def register(self, namespace, callback, priority=0, method_name=None):
"""Register a fileSending method
def register(self, manager, priority: int = 0) -> None:
"""Register a fileSending manager
@param namespace(unicode): XEP namespace
@param callback(callable): method to call (must have the same signature as [fileSend])
@param priority(int): pririoty of this method, the higher available will be used
@param method_name(unicode): short name for the method, namespace will be used if None
@param manager: object implementing canHandleFileSend, and fileSend methods
@param priority: pririoty of this manager, the higher available will be used
"""
for data in self._file_callbacks:
if namespace == data[0]:
raise exceptions.ConflictError(
"A method with this namespace is already registered"
)
self._file_callbacks.append(
(namespace, callback, priority, method_name or namespace)
)
self._file_callbacks.sort(key=lambda data: data[2], reverse=True)
m_data = (manager, priority)
if m_data in self._file_managers:
raise exceptions.ConflictError(
f"Manager {manager} is already registered"
)
if not hasattr(manager, "canHandleFileSend") or not hasattr(manager, "fileSend"):
raise ValueError(
f'{manager} must have both "canHandleFileSend" and "fileSend" methods to '
'be registered')
self._file_managers.append(m_data)
self._file_managers.sort(key=lambda m: m[1], reverse=True)
def unregister(self, namespace):
for idx, data in enumerate(self._file_callbacks):
if data[0] == namespace:
del [idx]
return
raise exceptions.NotFound("The namespace to unregister doesn't exist")
def unregister(self, manager):
for idx, data in enumerate(self._file_managers):
if data[0] == manager:
break
else:
raise exceptions.NotFound("The file manager {manager} is not registered")
del self._file_managers[idx]
# Dialogs with user
# the overwrite check is done here