1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-01 01:35:59 +01:00

Basic Sending/Receiving Files works again

This commit is contained in:
vanitasvitae 2017-07-29 22:21:36 +02:00
parent af069ffc49
commit 1dbdafe28c
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
9 changed files with 236 additions and 88 deletions

View file

@ -63,6 +63,18 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
} }
} }
public void notifyProgressListenersFinished() {
for (ProgressListener p : progressListeners) {
p.finished();
}
}
public void notifyProgressListenersStarted() {
for (ProgressListener p : progressListeners) {
p.started();
}
}
@Override @Override
public String getNamespace() { public String getNamespace() {
return JingleFileTransfer.NAMESPACE; return JingleFileTransfer.NAMESPACE;

View file

@ -17,8 +17,10 @@
package org.jivesoftware.smackx.jft.internal; package org.jivesoftware.smackx.jft.internal;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -41,6 +43,8 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer<RemoteFile>
private static final Logger LOGGER = Logger.getLogger(JingleIncomingFileOffer.class.getName()); private static final Logger LOGGER = Logger.getLogger(JingleIncomingFileOffer.class.getName());
private File target;
public JingleIncomingFileOffer(JingleFileTransferChildElement offer) { public JingleIncomingFileOffer(JingleFileTransferChildElement offer) {
super(new RemoteFile(offer)); super(new RemoteFile(offer));
} }
@ -52,15 +56,63 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer<RemoteFile>
@Override @Override
public void onTransportReady(BytestreamSession bytestreamSession) { 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 { try {
inputStream = bytestreamSession.getInputStream(); 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) { } catch (IOException e) {
LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession: " + e, 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 @Override
public boolean isOffer() { public boolean isOffer() {
return true; return true;
@ -75,6 +127,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer<RemoteFile>
public Future<Void> accept(XMPPConnection connection, File target) public Future<Void> accept(XMPPConnection connection, File target)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException { SmackException.NoResponseException {
this.target = target;
JingleSession session = getParent().getParent(); JingleSession session = getParent().getParent();
if (session.getSessionState() == JingleSession.SessionState.pending) { if (session.getSessionState() == JingleSession.SessionState.pending) {
session.accept(connection); session.accept(connection);

View file

@ -17,6 +17,12 @@
package org.jivesoftware.smackx.jft.internal; package org.jivesoftware.smackx.jft.internal;
import java.io.File; 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.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.jft.controller.OutgoingFileOfferController; 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. * Created by vanitas on 26.07.17.
*/ */
public class JingleOutgoingFileOffer extends AbstractJingleFileOffer<LocalFile> implements OutgoingFileOfferController { public class JingleOutgoingFileOffer extends AbstractJingleFileOffer<LocalFile> implements OutgoingFileOfferController {
private static final Logger LOGGER = Logger.getLogger(JingleOutgoingFileOffer.class.getName());
public JingleOutgoingFileOffer(File file) { public JingleOutgoingFileOffer(File file) {
super(new LocalFile(file)); super(new LocalFile(file));
@ -40,7 +47,34 @@ public class JingleOutgoingFileOffer extends AbstractJingleFileOffer<LocalFile>
@Override @Override
public void onTransportReady(BytestreamSession bytestreamSession) { 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 @Override

View file

@ -88,4 +88,8 @@ public class LocalFile extends AbstractJingleFileTransferFile {
public void setHashElement(HashElement hashElement) { public void setHashElement(HashElement hashElement) {
this.hashElement = hashElement; this.hashElement = hashElement;
} }
public File getFile() {
return file;
}
} }

View file

@ -28,6 +28,7 @@ import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.jingle.Callback; import org.jivesoftware.smackx.jingle.Callback;
@ -60,72 +61,11 @@ public class JingleContent implements JingleTransportCallback {
private JingleTransport<?> transport; private JingleTransport<?> transport;
private JingleSecurity<?> security; private JingleSecurity<?> security;
private JingleTransport<?> replaceTransport = null;
private final List<Callback> callbacks = Collections.synchronizedList(new ArrayList<Callback>()); private final List<Callback> callbacks = Collections.synchronizedList(new ArrayList<Callback>());
private final Set<String> transportBlacklist = Collections.synchronizedSet(new HashSet<String>()); private final Set<String> transportBlacklist = Collections.synchronizedSet(new HashSet<String>());
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 { public enum STATE {
pending_accept, pending_accept,
pending_transmission_start, 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()); 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) { public void addCallback(Callback callback) {
callbacks.add(callback); callbacks.add(callback);
} }
@ -282,9 +307,29 @@ public class JingleContent implements JingleTransportCallback {
getSenders() == JingleContentElement.Senders.both; 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 @Override
public void onTransportReady(BytestreamSession bytestreamSession) { public void onTransportReady(BytestreamSession bytestreamSession) {
LOGGER.log(Level.INFO, "TransportReady: " + (isReceiving() ? "Send" : "Receive"));
if (bytestreamSession == null) { if (bytestreamSession == null) {
throw new AssertionError("bytestreamSession MUST NOT be null at this point."); throw new AssertionError("bytestreamSession MUST NOT be null at this point.");
} }
@ -330,19 +375,6 @@ public class JingleContent implements JingleTransportCallback {
connection.createStanzaCollectorAndSend(transportReplace).nextResultOrThrow(); 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() { public static String randomName() {
return "cont-" + StringUtils.randomString(16); return "cont-" + StringUtils.randomString(16);
} }

View file

@ -110,6 +110,10 @@ public class JingleSession {
throw new IllegalStateException("Session is not in pending state."); throw new IllegalStateException("Session is not in pending state.");
} }
for (JingleContent content : contents.values()) {
content.onAccept(connection);
}
connection.createStanzaCollectorAndSend(createSessionAccept()).nextResultOrThrow(); connection.createStanzaCollectorAndSend(createSessionAccept()).nextResultOrThrow();
this.sessionState = SessionState.active; this.sessionState = SessionState.active;
} }

View file

@ -22,6 +22,7 @@ import java.util.List;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.jingle.callbacks.JingleTransportCallback; import org.jivesoftware.smackx.jingle.callbacks.JingleTransportCallback;
import org.jivesoftware.smackx.jingle.element.JingleContentTransportElement; import org.jivesoftware.smackx.jingle.element.JingleContentTransportElement;
@ -95,7 +96,7 @@ public abstract class JingleTransport<D extends JingleContentTransportElement> e
return peersProposal; return peersProposal;
} }
public abstract void handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping); public abstract IQ handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping);
public void setParent(JingleContent parent) { public void setParent(JingleContent parent) {
if (this.parent != parent) { if (this.parent != parent) {

View file

@ -16,9 +16,13 @@
*/ */
package org.jivesoftware.smackx.jingle.transport.jingle_ibb; 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.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.bytestreams.BytestreamSession; import org.jivesoftware.smackx.bytestreams.BytestreamSession;
@ -37,6 +41,7 @@ import org.jivesoftware.smackx.jingle.transport.jingle_ibb.element.JingleIBBTran
* Jingle InBandBytestream Transport component. * Jingle InBandBytestream Transport component.
*/ */
public class JingleIBBTransport extends JingleTransport<JingleIBBTransportElement> { public class JingleIBBTransport extends JingleTransport<JingleIBBTransportElement> {
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_V1 = "urn:xmpp:jingle:transports:ibb:1";
public static final String NAMESPACE = NAMESPACE_V1; public static final String NAMESPACE = NAMESPACE_V1;
@ -74,10 +79,11 @@ public class JingleIBBTransport extends JingleTransport<JingleIBBTransportElemen
@Override @Override
public void establishIncomingBytestreamSession(final XMPPConnection connection, final JingleTransportCallback callback, final JingleSession session) { public void establishIncomingBytestreamSession(final XMPPConnection connection, final JingleTransportCallback callback, final JingleSession session) {
final InBandBytestreamManager inBandBytestreamManager = InBandBytestreamManager.getByteStreamManager(connection); final InBandBytestreamManager inBandBytestreamManager = InBandBytestreamManager.getByteStreamManager(connection);
LOGGER.log(Level.INFO, "Listen for incoming IBB transports from " + session.getPeer() + ":" + getSid());
InBandBytestreamListener bytestreamListener = new InBandBytestreamListener() { InBandBytestreamListener bytestreamListener = new InBandBytestreamListener() {
@Override @Override
public void incomingBytestreamRequest(InBandBytestreamRequest request) { public void incomingBytestreamRequest(InBandBytestreamRequest request) {
LOGGER.log(Level.INFO, "Incoming IBB stream: " + request.getFrom().asFullJidIfPossible() + ":" + request.getSessionID());
if (request.getFrom().asFullJidIfPossible().equals(session.getPeer()) if (request.getFrom().asFullJidIfPossible().equals(session.getPeer())
&& request.getSessionID().equals(getSid())) { && request.getSessionID().equals(getSid())) {
@ -119,8 +125,8 @@ public class JingleIBBTransport extends JingleTransport<JingleIBBTransportElemen
} }
@Override @Override
public void handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) { public IQ handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) {
// Nothing to do. return IQ.createResultIQ(wrapping);
} }
@Override @Override

View file

@ -275,28 +275,30 @@ public class JingleS5BTransport extends JingleTransport<JingleS5BTransportElemen
} }
@Override @Override
public void handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) { public IQ handleTransportInfo(JingleContentTransportInfoElement info, JingleElement wrapping) {
switch (info.getElementName()) { switch (info.getElementName()) {
case JingleS5BTransportInfoElement.CandidateUsed.ELEMENT: case JingleS5BTransportInfoElement.CandidateUsed.ELEMENT:
handleCandidateUsed((JingleS5BTransportInfoElement) info, wrapping); handleCandidateUsed((JingleS5BTransportInfoElement) info, wrapping);
return; break;
case JingleS5BTransportInfoElement.CandidateActivated.ELEMENT: case JingleS5BTransportInfoElement.CandidateActivated.ELEMENT:
handleCandidateActivate((JingleS5BTransportInfoElement) info); handleCandidateActivate((JingleS5BTransportInfoElement) info);
return; break;
case JingleS5BTransportInfoElement.CandidateError.ELEMENT: case JingleS5BTransportInfoElement.CandidateError.ELEMENT:
handleCandidateError((JingleS5BTransportInfoElement) info); handleCandidateError((JingleS5BTransportInfoElement) info);
return; break;
case JingleS5BTransportInfoElement.ProxyError.ELEMENT: case JingleS5BTransportInfoElement.ProxyError.ELEMENT:
handleProxyError((JingleS5BTransportInfoElement) info); handleProxyError((JingleS5BTransportInfoElement) info);
return; break;
default: default:
throw new AssertionError("Unknown transport-info element: " + info.getElementName()); throw new AssertionError("Unknown transport-info element: " + info.getElementName());
} }
return IQ.createResultIQ(wrapping);
} }
private void handleCandidateUsed(JingleS5BTransportInfoElement info, JingleElement wrapping) { private void handleCandidateUsed(JingleS5BTransportInfoElement info, JingleElement wrapping) {