From 1dbdafe28c5d59afd34570b91b346b4e87317a14 Mon Sep 17 00:00:00 2001 From: vanitasvitae Date: Sat, 29 Jul 2017 22:21:36 +0200 Subject: [PATCH] Basic Sending/Receiving Files works again --- .../jft/internal/JingleFileTransfer.java | 12 ++ .../jft/internal/JingleIncomingFileOffer.java | 57 +++++- .../jft/internal/JingleOutgoingFileOffer.java | 34 ++++ .../smackx/jft/internal/file/LocalFile.java | 4 + .../jingle/components/JingleContent.java | 186 ++++++++++-------- .../jingle/components/JingleSession.java | 4 + .../jingle/components/JingleTransport.java | 3 +- .../jingle_ibb/JingleIBBTransport.java | 12 +- .../jingle_s5b/JingleS5BTransport.java | 12 +- 9 files changed, 236 insertions(+), 88 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleFileTransfer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleFileTransfer.java index 4df3f8fe8..e9174a997 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleFileTransfer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleFileTransfer.java @@ -63,6 +63,18 @@ public abstract class JingleFileTransfer extends JingleDescription private static final Logger LOGGER = Logger.getLogger(JingleIncomingFileOffer.class.getName()); + private File target; + public JingleIncomingFileOffer(JingleFileTransferChildElement offer) { super(new RemoteFile(offer)); } @@ -52,13 +56,61 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer @Override public void onTransportReady(BytestreamSession bytestreamSession) { - InputStream inputStream; + LOGGER.log(Level.INFO, "Receive file to " + target.getAbsolutePath()); + File mFile = target; + if (!mFile.exists()) { + try { + mFile.createNewFile(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not create new File!"); + } + } + + InputStream inputStream = null; + OutputStream outputStream = null; try { inputStream = bytestreamSession.getInputStream(); + outputStream = new FileOutputStream(mFile); + + byte[] filebuf = new byte[(int) file.getSize()]; + int read = 0; + byte[] bufbuf = new byte[4096]; + LOGGER.log(Level.INFO, "Begin receiving bytes."); + while (read < filebuf.length) { + int r = inputStream.read(bufbuf); + if (r >= 0) { + System.arraycopy(bufbuf, 0, filebuf, read, r); + read += r; + LOGGER.log(Level.INFO, "Read " + r + " (" + read + " of " + filebuf.length + ") bytes."); + } else { + break; + } + } + + outputStream.write(filebuf); + outputStream.flush(); + } catch (IOException e) { LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession: " + e, e); - return; + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close InputStream: " + e, e); + } + } + + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close OutputStream: " + e, e); + } + } } + + notifyProgressListenersFinished(); } @Override @@ -75,6 +127,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer public Future accept(XMPPConnection connection, File target) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + this.target = target; JingleSession session = getParent().getParent(); if (session.getSessionState() == JingleSession.SessionState.pending) { session.accept(connection); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileOffer.java index 4cdc3df03..fe636c7b2 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileOffer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/JingleOutgoingFileOffer.java @@ -17,6 +17,12 @@ package org.jivesoftware.smackx.jft.internal; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.jft.controller.OutgoingFileOfferController; @@ -28,6 +34,7 @@ import org.jivesoftware.smackx.jingle.element.JingleElement; * Created by vanitas on 26.07.17. */ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer implements OutgoingFileOfferController { + private static final Logger LOGGER = Logger.getLogger(JingleOutgoingFileOffer.class.getName()); public JingleOutgoingFileOffer(File file) { super(new LocalFile(file)); @@ -40,7 +47,34 @@ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer @Override public void onTransportReady(BytestreamSession bytestreamSession) { + File mFile = ((LocalFile) file).getFile(); + OutputStream outputStream = null; + InputStream inputStream = null; + try { + outputStream = bytestreamSession.getOutputStream(); + inputStream = new FileInputStream(mFile); + + byte[] fileBuf = new byte[(int) mFile.length()]; + + inputStream.read(fileBuf); + + outputStream.write(fileBuf); + outputStream.flush(); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Exception while sending file: " + e, e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close FileInputStream: " + e, e); + } + } + } + + notifyProgressListenersFinished(); } @Override diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/file/LocalFile.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/file/LocalFile.java index d639f0c29..07fe11849 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/file/LocalFile.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jft/internal/file/LocalFile.java @@ -88,4 +88,8 @@ public class LocalFile extends AbstractJingleFileTransferFile { public void setHashElement(HashElement hashElement) { this.hashElement = hashElement; } + + public File getFile() { + return file; + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleContent.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleContent.java index 39a4386ee..589327a29 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleContent.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleContent.java @@ -28,6 +28,7 @@ import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.Async; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.jingle.Callback; @@ -60,72 +61,11 @@ public class JingleContent implements JingleTransportCallback { private JingleTransport transport; private JingleSecurity security; + private JingleTransport replaceTransport = null; + private final List callbacks = Collections.synchronizedList(new ArrayList()); private final Set transportBlacklist = Collections.synchronizedSet(new HashSet()); - public IQ handleJingleRequest(JingleElement request, XMPPConnection connection) { - switch (request.getAction()) { - case content_modify: - return handleContentModify(request, connection); - case description_info: - return handleDescriptionInfo(request, connection); - case security_info: - return handleSecurityInfo(request, connection); - case session_info: - return handleSessionInfo(request, connection); - case transport_accept: - return handleTransportAccept(request, connection); - case transport_info: - return handleTransportInfo(request, connection); - case transport_reject: - return handleTransportReject(request, connection); - case transport_replace: - return handleTransportReplace(request, connection); - default: - throw new AssertionError("Illegal jingle action: " + request.getAction() + " is not allowed here."); - } - } - - public IQ handleSessionAccept(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleContentModify(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleDescriptionInfo(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public void handleContentRemove(JingleSession session, XMPPConnection connection) { - - } - - public IQ handleSecurityInfo(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleSessionInfo(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleTransportAccept(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleTransportInfo(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleTransportReject(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - - public IQ handleTransportReplace(JingleElement request, XMPPConnection connection) { - return IQ.createResultIQ(request); - } - public enum STATE { pending_accept, pending_transmission_start, @@ -191,6 +131,91 @@ public class JingleContent implements JingleTransportCallback { return new JingleContent(description, transport, security, content.getName(), content.getDisposition(), content.getCreator(), content.getSenders()); } + /* HANDLEXYZ */ + + public IQ handleJingleRequest(JingleElement request, XMPPConnection connection) { + switch (request.getAction()) { + case content_modify: + return handleContentModify(request, connection); + case description_info: + return handleDescriptionInfo(request, connection); + case security_info: + return handleSecurityInfo(request, connection); + case session_info: + return handleSessionInfo(request, connection); + case transport_accept: + return handleTransportAccept(request, connection); + case transport_info: + return handleTransportInfo(request, connection); + case transport_reject: + return handleTransportReject(request, connection); + case transport_replace: + return handleTransportReplace(request, connection); + default: + throw new AssertionError("Illegal jingle action: " + request.getAction() + " is not allowed here."); + } + } + + public void handleContentAccept(JingleElement request, XMPPConnection connection) { + onAccept(connection); + } + + + public IQ handleSessionAccept(JingleElement request, XMPPConnection connection) { + LOGGER.log(Level.INFO, "RECEIVED SESSION ACCEPT!"); + onAccept(connection); + return IQ.createResultIQ(request); + } + + public IQ handleContentModify(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + public IQ handleDescriptionInfo(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + public void handleContentRemove(JingleSession session, XMPPConnection connection) { + + } + + public IQ handleSecurityInfo(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + public IQ handleSessionInfo(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + public IQ handleTransportAccept(JingleElement request, XMPPConnection connection) { + if (replaceTransport == null) { + LOGGER.log(Level.WARNING, "Received transport-accept, but apparently we did not try to replace the transport."); + return JingleElement.createJingleErrorOutOfOrder(request); + } + transport = replaceTransport; + + onAccept(connection); + + return IQ.createResultIQ(request); + } + + public IQ handleTransportInfo(JingleElement request, XMPPConnection connection) { + assert request.getContents().size() == 1; + JingleContentElement content = request.getContents().get(0); + + return transport.handleTransportInfo(content.getTransport().getInfo(), request); + } + + public IQ handleTransportReject(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + public IQ handleTransportReplace(JingleElement request, XMPPConnection connection) { + return IQ.createResultIQ(request); + } + + /* MISCELLANEOUS */ + public void addCallback(Callback callback) { callbacks.add(callback); } @@ -282,9 +307,29 @@ public class JingleContent implements JingleTransportCallback { getSenders() == JingleContentElement.Senders.both; } + public void onAccept(final XMPPConnection connection) { + //Establish transport + Async.go(new Runnable() { + @Override + public void run() { + try { + if (isReceiving()) { + LOGGER.log(Level.INFO, "Establish incoming bytestream."); + getTransport().establishIncomingBytestreamSession(connection, JingleContent.this, getParent()); + } else if (isSending()) { + LOGGER.log(Level.INFO, "Establish outgoing bytestream."); + getTransport().establishOutgoingBytestreamSession(connection, JingleContent.this, getParent()); + } + } catch (SmackException.NotConnectedException | InterruptedException e) { + LOGGER.log(Level.SEVERE, "Error establishing connection: " + e, e); + } + } + }); + } + @Override public void onTransportReady(BytestreamSession bytestreamSession) { - + LOGGER.log(Level.INFO, "TransportReady: " + (isReceiving() ? "Send" : "Receive")); if (bytestreamSession == null) { throw new AssertionError("bytestreamSession MUST NOT be null at this point."); } @@ -330,19 +375,6 @@ public class JingleContent implements JingleTransportCallback { connection.createStanzaCollectorAndSend(transportReplace).nextResultOrThrow(); } - public void handleContentAccept(JingleElement request, XMPPConnection connection) { - //Establish transport - try { - if (isReceiving()) { - getTransport().establishIncomingBytestreamSession(connection, this, getParent()); - } else if (isSending()) { - getTransport().establishOutgoingBytestreamSession(connection, this, getParent()); - } - } catch (SmackException.NotConnectedException | InterruptedException e) { - LOGGER.log(Level.SEVERE, "Error establishing connection: " + e, e); - } - } - public static String randomName() { return "cont-" + StringUtils.randomString(16); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleSession.java index fbfa7ba5a..b2ce59881 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleSession.java @@ -110,6 +110,10 @@ public class JingleSession { throw new IllegalStateException("Session is not in pending state."); } + for (JingleContent content : contents.values()) { + content.onAccept(connection); + } + connection.createStanzaCollectorAndSend(createSessionAccept()).nextResultOrThrow(); this.sessionState = SessionState.active; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleTransport.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleTransport.java index 72ba4ae6d..72b8d83e5 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleTransport.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/components/JingleTransport.java @@ -22,6 +22,7 @@ import java.util.List; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.jingle.callbacks.JingleTransportCallback; import org.jivesoftware.smackx.jingle.element.JingleContentTransportElement; @@ -95,7 +96,7 @@ public abstract class JingleTransport e return peersProposal; } - public abstract void handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping); + public abstract IQ handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping); public void setParent(JingleContent parent) { if (this.parent != parent) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transport/jingle_ibb/JingleIBBTransport.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transport/jingle_ibb/JingleIBBTransport.java index 90655de89..91bb26193 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transport/jingle_ibb/JingleIBBTransport.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transport/jingle_ibb/JingleIBBTransport.java @@ -16,9 +16,13 @@ */ package org.jivesoftware.smackx.jingle.transport.jingle_ibb; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.bytestreams.BytestreamSession; @@ -37,6 +41,7 @@ import org.jivesoftware.smackx.jingle.transport.jingle_ibb.element.JingleIBBTran * Jingle InBandBytestream Transport component. */ public class JingleIBBTransport extends JingleTransport { + private static final Logger LOGGER = Logger.getLogger(JingleIBBTransport.class.getName()); public static final String NAMESPACE_V1 = "urn:xmpp:jingle:transports:ibb:1"; public static final String NAMESPACE = NAMESPACE_V1; @@ -74,10 +79,11 @@ public class JingleIBBTransport extends JingleTransport