package it.yup.xmpp;
import java.util.Vector;
import org.bouncycastle.util.encoders.Base64;
import it.yup.util.Utils;
import it.yup.xmpp.packets.Iq;
import it.yup.xmpp.packets.Presence;
import it.yup.xml.Element;
import it.yup.xmlstream.BasicXmlStream;
import it.yup.xmlstream.EventQuery;
import it.yup.xmlstream.PacketListener;
public class FTSender {
/*
* Initiate a session with jingle
*/
public static String SESSION_INITIATE = "session-initiate";
/*
* Initiate a session with jingle
*/
public static String SESSION_ACCEPT = "session-accept";
/*
* Initiate a session with jingle
*/
public static String SESSION_TERMINATE = "session-terminate";
/*
* Jingle related action
*/
public static String ACTION = "action";
/*
* Jingle related DATA
*/
public static String DATA = "data";
/*
* Jingle related jingle
*/
public static String JINGLE = "jingle";
/*
* Jingle related jingle
*/
public static String DECLINE = "decline";
/*
* Jingle related action
*/
public static String CONTENT = "content";
/*
* Jingle related description
*/
public static String DESCRIPTION = "description";
/*
* Jingle related description
*/
public static String DESC = "desc";
/*
* Jingle related description
*/
public static String OFFER = "offer";
/*
* Jingle related name
*/
public static String NAME = "name";
/*
* Jingle related name
*/
public static String BLOCK_SIZE = "block-size";
/*
* Jingle related creator
*/
public static String CREATOR = "creator";
/*
* Jingle related initiator
*/
public static String INITIATOR = "initiator";
/*
* Jingle related SID
*/
public static String SID = "sid";
/*
* IBB related Seq
*/
public static String SEQ = "seq";
/*
* Jingle related file
*/
public static String FILE = "file";
/*
* Jingle related file
*/
public static String OPEN = "open";
/*
* Jingle related file
*/
public static String CLOSE = "close";
/*
* Jingle related file
*/
public static String TRANSPORT = "transport";
/*
* Jingle related size
*/
public static String SIZE = "size";
/*
* The name of the file to transfer
*/
public String fileName = "";
/*
* The xmpp client
*/
private XMPPClient xmppClient = XMPPClient.getInstance();
/*
* The data bytes of the file
*/
private byte[] fileData = null;
/*
* the chunk length
*/
private int chuck_length = 4096;
/*
* The receiver of the file
*/
private String to = null;
/*
* The description associated to the file
*/
private String desc = null;
/*
* The Session id related to this section
*/
private String jingleSid = "";
/*
* The Session id related to this section
*/
private String transportSid = "";
/*
* The session must be opened and the client must have sent session acept
* before sending file
*/
private boolean sessionOpened = false;
private FTSEventHandler eh;
public static boolean supportFT(String fullJid) {
Contact c = XMPPClient.getInstance().getRoster().getContactByJid(
fullJid);
if (c == null) return false;
Presence p = c.getPresence(fullJid);
if (p == null) return false;
Element caps = c.getCapabilities(p);
return supportFT(caps);
}
public static boolean supportFT(Element caps) {
if (caps == null) return false;
Element[] features = caps.getChildrenByName(null, "feature");
Vector vars = new Vector(features.length);
for (int i = 0; i < features.length; i++) {
Element ithFeature = features[i];
vars.addElement(ithFeature.getAttribute("var"));
}
if (vars.contains(XMPPClient.JINGLE) == false
|| vars.contains(XMPPClient.JINGLE_FILE_TRANSFER) == false
|| vars.contains(XMPPClient.JINGLE_IBB_TRANSPORT) == false ) return false;
return true;
}
public interface FTSEventHandler {
public void fileAcceptance(Contact c, String fileName, boolean accept, FTSender sender);
public void fileError(Contact c, String fileName, Element e);
public void fileSent(Contact c, String fileName, boolean b,FTSender sender);
public void chunkSent(int sentBytes, int length,FTSender sender);
public void sessionInitated(Contact contactByJid, String fileName,
FTSender sender);
}
/*
* The constructor that initiate a Jingle file transfer
*/
public FTSender(String fileName, byte[] fileData, String to, String desc, FTSEventHandler eh) {
this.fileName = fileName;
this.fileData = fileData;
this.to = to;
this.desc = desc;
this.eh = eh;
}
public void sessionInitiate() {
Iq sessionInitiateIq = new Iq(this.to, Iq.T_SET);
Element jingle = sessionInitiateIq.addElement(XMPPClient.JINGLE,
FTSender.JINGLE);
jingle.setAttribute(ACTION, SESSION_INITIATE);
jingle.setAttribute(INITIATOR, xmppClient.my_jid);
String encodedSid = Utils.hexDigest(System.currentTimeMillis() + "",
"sha1");
jingleSid = new String(encodedSid);
jingle.setAttribute(SID, jingleSid);
Element content = jingle.addElement(null, CONTENT);
content.setAttribute(CREATOR, INITIATOR);
content.setAttribute(NAME, "a-file-transfer" + jingleSid);
Element description = content.addElement(
XMPPClient.JINGLE_FILE_TRANSFER, DESCRIPTION);
Element offer = description.addElement(null, OFFER);
Element file = offer.addElement(XMPPClient.FILE_TRANSFER, FILE);
file.setAttribute(NAME, this.fileName);
if (fileData != null) file
.setAttribute(SIZE, this.fileData.length + "");
if (desc != null && desc.length()>0)
file.addElement(null, DESC).addText(this.desc);
Element transport = content.addElement(XMPPClient.JINGLE_IBB_TRANSPORT,
TRANSPORT);
transport.setAttribute(FTSender.BLOCK_SIZE, this.chuck_length + "");
encodedSid = Utils.hexDigest((System.currentTimeMillis() + 1) + "",
"sha1");
transportSid = new String(encodedSid);
transport.setAttribute(FTSender.SID, transportSid);
IQResultListener sessionInitiateListener = new IQResultListener() {
public void handleError(Element e) {
eh.fileError(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, e);
}
public void handleResult(Element e) {
Element jingle = e.getChildByName(XMPPClient.JINGLE,
FTSender.JINGLE);
if (jingle != null) {
Element decline = jingle.getChildByName(null,
FTSender.DECLINE);
if (decline != null) {
eh.fileAcceptance(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, false,FTSender.this);
return;
}
}
FTSender.this.initiateInteraction();
}
};
PacketListener terminateListener = new PacketListener() {
public void packetReceived(Element e) {
Element jingle = e.getChildByName(XMPPClient.JINGLE,
FTSender.JINGLE);
if (jingle != null) {
Element decline = jingle.getChildByName(null,
FTSender.DECLINE);
if (decline != null) {
eh.fileAcceptance(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, false,FTSender.this);
return;
}
}
}
};
//this.encodedData.setLength(fileSize*2);
EventQuery eq = new EventQuery(Iq.IQ, new String[] { Iq.ATT_FROM,
Iq.ATT_TYPE }, new String[] { this.to, Iq.T_SET });
EventQuery eqChild = eq.child = new EventQuery(FTSender.JINGLE,
new String[] { FTSender.ACTION },
new String[] { FTSender.SESSION_TERMINATE });
eqChild.child = new EventQuery(FTSender.DECLINE, null, null);
BasicXmlStream.addEventListener(eq, terminateListener);
xmppClient.sendIQ(sessionInitiateIq, sessionInitiateListener);
eh.sessionInitated(XMPPClient.getInstance().getRoster()
.getContactByJid(this.to), fileName, this);
}
private void initiateInteraction() {
EventQuery eq = new EventQuery(Iq.IQ, new String[] { Iq.ATT_TYPE },
new String[] { Iq.T_SET });
eq.child = new EventQuery(FTSender.JINGLE, new String[] { "xmlns",
FTSender.ACTION, FTSender.SID }, new String[] {
XMPPClient.JINGLE, FTSender.SESSION_ACCEPT,
FTSender.this.jingleSid });
PacketListener ibbSender = new PacketListener() {
public void packetReceived(Element e) {
eh.fileAcceptance(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, true,FTSender.this);
Element recIq = e;
Iq reply = Utils.easyReply(recIq);
xmppClient.sendPacket(reply);
if (sessionOpened) FTSender.this.sendFile();
// this iq must have arrived and the packet received below
sessionOpened = true;
}
};
BasicXmlStream.addOnetimeEventListener(eq, ibbSender);
Iq initiateInteraction = new Iq(this.to, Iq.T_SET);
Element open = initiateInteraction.addElement(XMPPClient.NS_IBB, OPEN);
open.setAttribute(SID, transportSid);
open.setAttribute(BLOCK_SIZE, chuck_length + "");
IQResultListener initiateInteractionListener = new IQResultListener() {
public void handleError(Element e) {
eh.fileAcceptance(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, false,FTSender.this);
}
public void handleResult(Element e) {
if (sessionOpened) FTSender.this.sendFile();
// this result must have arrived and the packet received below
sessionOpened = true;
}
};
xmppClient.sendIQ(initiateInteraction, initiateInteractionListener);
}
private int fileOffset = 0;
private String encodedData = "";
private IQResultListener chunkListener;
private int seq = 0;
private void sendFile() {
this.fileOffset = 0;
this.encodedData = new String(Base64.encode(this.fileData));
chunkListener = new IQResultListener() {
public void handleError(Element e) {
}
public void handleResult(Element e) {
if (fileOffset < encodedData.length()) FTSender.this
.sendChunk();
else
FTSender.this.sendFooter();
}
};
sendChunk();
}
private void sendChunk() {
Iq chunkIq = new Iq(this.to, Iq.T_SET);
Element data = chunkIq.addElement(XMPPClient.NS_IBB, DATA);
data.setAttribute(SID, transportSid);
data.setAttribute(SEQ, seq + "");
seq++;
if (seq == 65536) seq = 0;
int endIndex = Math
.min(fileOffset + chuck_length, encodedData.length());
String substr = this.encodedData.substring(fileOffset, endIndex);
fileOffset += chuck_length;
data.addText(substr);
xmppClient.sendIQ(chunkIq, chunkListener);
eh.chunkSent(fileOffset + chuck_length,encodedData.length(),FTSender.this);
}
private void sendFooter() {
Iq closeInteraction = new Iq(this.to, Iq.T_SET);
Element close = closeInteraction.addElement(XMPPClient.NS_IBB, CLOSE);
close.setAttribute(SID, transportSid);
IQResultListener closeListener = new IQResultListener() {
public void handleError(Element e) {
eh.fileSent(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, true,FTSender.this);
}
public void handleResult(Element e) {
sessionOpened = false;
eh.fileSent(XMPPClient.getInstance().getRoster()
.getContactByJid(e.getAttribute(Iq.ATT_FROM)),
fileName, true,FTSender.this);
}
};
// first setup the listener to SESSION_TERMINATE
PacketListener terminateListener = new PacketListener() {
public void packetReceived(Element e) {
Iq reply = Utils.easyReply(e);
xmppClient.sendPacket(reply);
}
};
EventQuery eq = new EventQuery(Iq.IQ, new String[] { Iq.ATT_FROM,
Iq.ATT_TYPE }, new String[] { this.to, Iq.T_SET });
eq.child = new EventQuery(FTSender.JINGLE, new String[] {
FTSender.ACTION, "xmlns" }, new String[] {
FTSender.SESSION_TERMINATE, XMPPClient.JINGLE });
BasicXmlStream.addOnetimeEventListener(eq, terminateListener);
xmppClient.sendIQ(closeInteraction, closeListener);
}
}