Mercurial > libervia-backend
diff src/plugins/plugin_xep_0096.py @ 223:86d249b6d9b7
Files reorganisation
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 29 Dec 2010 01:06:29 +0100 |
parents | plugins/plugin_xep_0096.py@f271fff3a713 |
children | b1794cbb88e5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_xep_0096.py Wed Dec 29 01:06:29 2010 +0100 @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +SAT plugin for managing xep-0096 +Copyright (C) 2009, 2010 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 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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + +from logging import debug, info, error +from twisted.words.xish import domish +from twisted.internet import protocol +from twisted.words.protocols.jabber import client, jid +from twisted.words.protocols.jabber import error as jab_error +import os.path +from twisted.internet import reactor #FIXME best way ??? +import pdb + +from zope.interface import implements + +try: + from twisted.words.protocols.xmlstream import XMPPHandler +except ImportError: + from wokkel.subprotocols import XMPPHandler + +from wokkel import disco, iwokkel + +IQ_SET = '/iq[@type="set"]' +NS_SI = 'http://jabber.org/protocol/si' +SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]' + +PLUGIN_INFO = { +"name": "XEP 0096 Plugin", +"import_name": "XEP_0096", +"type": "XEP", +"protocols": ["XEP-0096"], +"dependencies": ["XEP_0065"], +"main": "XEP_0096", +"handler": "yes", +"description": _("""Implementation of SI File Transfert""") +} + +class XEP_0096(): + + def __init__(self, host): + info(_("Plugin XEP_0096 initialization")) + self.host = host + self._waiting_for_approval = {} + host.bridge.addMethod("sendFile", ".communication", in_sign='sss', out_sign='s', method=self.sendFile) + + def getHandler(self, profile): + return XEP_0096_handler(self) + + def xep_96(self, IQ, profile): + info (_("XEP-0096 management")) + IQ.handled=True + SI_elem = IQ.firstChildElement() + debug(SI_elem.toXml()) + filename = "" + file_size = "" + for element in SI_elem.elements(): + if element.name == "file": + info (_("File proposed: name=[%(name)s] size=%(size)s") % {'name':element['name'], 'size':element['size']}) + filename = element["name"] + file_size = element["size"] + elif element.name == "feature": + from_jid = IQ["from"] + self._waiting_for_approval[IQ["id"]] = (element, from_jid, file_size, profile) + data={ "filename":filename, "from":from_jid, "size":file_size } + self.host.askConfirmation(IQ["id"], "FILE_TRANSFERT", data, self.confirmationCB) + + def confirmationCB(self, id, accepted, data): + """Called on confirmation answer""" + if accepted: + data['size'] = self._waiting_for_approval[id][2] + self.host.plugins["XEP_0065"].setData(data, id) + self.approved(id) + else: + debug (_("Transfert [%s] refused"), id) + del(self._waiting_for_approval[id]) + + def approved(self, id): + """must be called when a file transfert has be accepted by client""" + debug (_("Transfert [%s] accepted"), id) + + if ( not self._waiting_for_approval.has_key(id) ): + error (_("Approved unknow id !")) + #TODO: manage this (maybe approved by several frontends) + else: + element, from_id, size, profile = self._waiting_for_approval[id] + del(self._waiting_for_approval[id]) + self.negociate(element, id, from_id, profile) + + def negociate(self, feat_elem, id, to_jid, profile): + #TODO: put this in a plugin + #FIXME: over ultra mega ugly, need to be generic + client = self.host.getClient(profile) + assert(client) + info (_("Feature negociation")) + data = feat_elem.firstChildElement() + field = data.firstChildElement() + #FIXME: several options ! Q&D code for test only + option = field.firstChildElement() + value = option.firstChildElement() + if unicode(value) == "http://jabber.org/protocol/bytestreams": + #ugly, as usual, need to be entirely rewritten (just for test !) + result = domish.Element(('', 'iq')) + result['type'] = 'result' + result['id'] = id + result['to'] = to_jid + si = result.addElement('si', 'http://jabber.org/protocol/si') + file = si.addElement('file', 'http://jabber.org/protocol/si/profile/file-transfer') + feature = si.addElement('feature', 'http://jabber.org/protocol/feature-neg') + x = feature.addElement('x', 'jabber:x:data') + x['type'] = 'submit' + field = x.addElement('field') + field['var'] = 'stream-method' + value = field.addElement('value') + value.addContent('http://jabber.org/protocol/bytestreams') + client.xmlstream.send(result) + + def fileCB(self, answer, xmlstream, current_jid): + if answer['type']=="result": #FIXME FIXME FIXME ugly ugly ugly ! and temp FIXME FIXME FIXME + info("SENDING UGLY ANSWER") + offer=client.IQ(xmlstream,'set') + offer["from"]=current_jid.full() + offer["to"]=answer['from'] + query=offer.addElement('query', 'http://jabber.org/protocol/bytestreams') + query['mode']='tcp' + streamhost=query.addElement('streamhost') + streamhost['host']=self.host.memory.getParamA("IP", "File Transfert") + streamhost['port']=self.host.memory.getParamA("Port", "File Transfert") + streamhost['jid']=current_jid.full() + offer.send() + + def sendFile(self, to, filepath, profile_key='@DEFAULT@'): + """send a file using XEP-0096 + Return an unique id to identify the transfert + """ + current_jid, xmlstream = self.host.getJidNStream(profile_key) + if not xmlstream: + error (_('Asking for an non-existant or not connected profile')) + return "" + debug ("sendfile (%s) to %s", filepath, to ) + print type(filepath), type(to) + + statinfo = os.stat(filepath) + + offer=client.IQ(xmlstream,'set') + debug ("Transfert ID: %s", offer["id"]) + + self.host.plugins["XEP_0065"].sendFile(offer["id"], filepath, str(statinfo.st_size)) + + offer["from"]=current_jid.full() + offer["to"]=jid.JID(to).full() + si=offer.addElement('si','http://jabber.org/protocol/si') + si["mime-type"]='text/plain' + si["profile"]='http://jabber.org/protocol/si/profile/file-transfer' + file = si.addElement('file', 'http://jabber.org/protocol/si/profile/file-transfer') + file['name']=os.path.basename(filepath) + file['size']=str(statinfo.st_size) + + ### + # FIXME: Ugly temporary hard coded implementation of XEP-0020 & XEP-0004, + # Need to be recoded elsewhere in a more generic way + ### + + feature=si.addElement('feature', "http://jabber.org/protocol/feature-neg") + x=feature.addElement('x', "jabber:x:data") + x['type']='form' + field=x.addElement('field') + field['type']='list-single' + field['var']='stream-method' + option = field.addElement('option') + value = option.addElement('value', content='http://jabber.org/protocol/bytestreams') + + offer.addCallback(self.fileCB, current_jid = current_jid, xmlstream = xmlstream) + offer.send() + return offer["id"] #XXX: using IQ id as file transfert id seems OK as IQ id are required + +class XEP_0096_handler(XMPPHandler): + implements(iwokkel.IDisco) + + def __init__(self, plugin_parent): + self.plugin_parent = plugin_parent + self.host = plugin_parent.host + + def connectionInitialized(self): + self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.xep_96, profile = self.parent.profile) + + def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + return [disco.DiscoFeature(NS_SI)] + + def getDiscoItems(self, requestor, target, nodeIdentifier=''): + return [] +