Cancel declines file offers
This commit is contained in:
parent
a08e835a52
commit
6c51424949
|
@ -20,7 +20,12 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smackx.jingle.component.JingleDescription;
|
import org.jivesoftware.smackx.jingle.component.JingleDescription;
|
||||||
|
import org.jivesoftware.smackx.jingle.component.JingleSession;
|
||||||
|
import org.jivesoftware.smackx.jingle.element.JingleElement;
|
||||||
|
import org.jivesoftware.smackx.jingle.element.JingleReasonElement;
|
||||||
import org.jivesoftware.smackx.jingle_filetransfer.controller.JingleFileTransferController;
|
import org.jivesoftware.smackx.jingle_filetransfer.controller.JingleFileTransferController;
|
||||||
import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement;
|
import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement;
|
||||||
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
|
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
|
||||||
|
@ -33,9 +38,6 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
|
||||||
public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5";
|
public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5";
|
||||||
public static final String NAMESPACE = NAMESPACE_V5;
|
public static final String NAMESPACE = NAMESPACE_V5;
|
||||||
|
|
||||||
public abstract boolean isOffer();
|
|
||||||
public abstract boolean isRequest();
|
|
||||||
|
|
||||||
protected State state;
|
protected State state;
|
||||||
protected JingleFileTransferFile file;
|
protected JingleFileTransferFile file;
|
||||||
|
|
||||||
|
@ -45,6 +47,10 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
|
||||||
this.file = file;
|
this.file = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract boolean isOffer();
|
||||||
|
|
||||||
|
public abstract boolean isRequest();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addProgressListener(ProgressListener listener) {
|
public void addProgressListener(ProgressListener listener) {
|
||||||
progressListeners.add(listener);
|
progressListeners.add(listener);
|
||||||
|
@ -57,8 +63,24 @@ public abstract class JingleFileTransfer extends JingleDescription<JingleFileTra
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
public void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException {
|
||||||
//TODO
|
JingleSession session = getParent().getParent();
|
||||||
|
switch (state) {
|
||||||
|
case pending:
|
||||||
|
if (session.isResponder()) {
|
||||||
|
connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.decline));
|
||||||
|
} else {
|
||||||
|
connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.cancel));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case active:
|
||||||
|
connection.createStanzaCollectorAndSend(JingleElement.createSessionTerminate(session.getPeer(), session.getSessionId(), JingleReasonElement.Reason.cancel));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
getParent().onContentCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyProgressListeners(float progress) {
|
public void notifyProgressListeners(float progress) {
|
||||||
|
|
|
@ -21,6 +21,9 @@ 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.io.OutputStream;
|
||||||
|
import java.security.DigestInputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -28,6 +31,8 @@ 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.smackx.bytestreams.BytestreamSession;
|
import org.jivesoftware.smackx.bytestreams.BytestreamSession;
|
||||||
|
import org.jivesoftware.smackx.hashes.HashManager;
|
||||||
|
import org.jivesoftware.smackx.hashes.element.HashElement;
|
||||||
import org.jivesoftware.smackx.jingle.component.JingleSession;
|
import org.jivesoftware.smackx.jingle.component.JingleSession;
|
||||||
import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement;
|
import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement;
|
||||||
import org.jivesoftware.smackx.jingle.element.JingleElement;
|
import org.jivesoftware.smackx.jingle.element.JingleElement;
|
||||||
|
@ -45,6 +50,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
|
|
||||||
public JingleIncomingFileOffer(JingleFileTransferChildElement offer) {
|
public JingleIncomingFileOffer(JingleFileTransferChildElement offer) {
|
||||||
super(new JingleFileTransferFile.RemoteFile(offer));
|
super(new JingleFileTransferFile.RemoteFile(offer));
|
||||||
|
this.state = State.pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,12 +64,25 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
throw new IllegalStateException("Target OutputStream is null");
|
throw new IllegalStateException("Target OutputStream is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = State.active;
|
||||||
|
|
||||||
|
HashElement hashElement = file.getHashElement();
|
||||||
|
MessageDigest digest = null;
|
||||||
|
if (hashElement != null) {
|
||||||
|
digest = HashManager.getMessageDigest(hashElement.getAlgorithm());
|
||||||
|
LOGGER.log(Level.INFO, "File offer had checksum: " + digest.toString());
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.log(Level.INFO, "Receive file");
|
LOGGER.log(Level.INFO, "Receive file");
|
||||||
|
|
||||||
InputStream inputStream = null;
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
inputStream = bytestreamSession.getInputStream();
|
inputStream = bytestreamSession.getInputStream();
|
||||||
|
|
||||||
|
if (digest != null) {
|
||||||
|
inputStream = new DigestInputStream(inputStream, digest);
|
||||||
|
}
|
||||||
|
|
||||||
int length = 0;
|
int length = 0;
|
||||||
int read = 0;
|
int read = 0;
|
||||||
byte[] bufbuf = new byte[4096];
|
byte[] bufbuf = new byte[4096];
|
||||||
|
@ -79,6 +98,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
} 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);
|
||||||
} finally {
|
} finally {
|
||||||
|
state = State.ended;
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
|
@ -97,7 +117,17 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (digest != null) {
|
||||||
|
byte[] mDigest = ((DigestInputStream) inputStream).getMessageDigest().digest();
|
||||||
|
if (!Arrays.equals(hashElement.getHash(), mDigest)) {
|
||||||
|
LOGGER.log(Level.WARNING, "CHECKSUM MISMATCH!");
|
||||||
|
} else {
|
||||||
|
LOGGER.log(Level.INFO, "CHECKSUM MATCHED :)");
|
||||||
|
}
|
||||||
|
}
|
||||||
notifyProgressListenersFinished();
|
notifyProgressListenersFinished();
|
||||||
|
getParent().onContentFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,6 +144,7 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
public void accept(XMPPConnection connection, File target)
|
public void accept(XMPPConnection connection, File target)
|
||||||
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
SmackException.NoResponseException, IOException {
|
SmackException.NoResponseException, IOException {
|
||||||
|
state = State.negotiating;
|
||||||
|
|
||||||
if (!target.exists()) {
|
if (!target.exists()) {
|
||||||
target.createNewFile();
|
target.createNewFile();
|
||||||
|
@ -131,6 +162,8 @@ public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements
|
||||||
public void accept(XMPPConnection connection, OutputStream stream)
|
public void accept(XMPPConnection connection, OutputStream stream)
|
||||||
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
SmackException.NoResponseException {
|
SmackException.NoResponseException {
|
||||||
|
state = State.negotiating;
|
||||||
|
|
||||||
target = stream;
|
target = stream;
|
||||||
|
|
||||||
JingleSession session = getParent().getParent();
|
JingleSession session = getParent().getParent();
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.jingle_filetransfer.controller;
|
package org.jivesoftware.smackx.jingle_filetransfer.controller;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smackx.jingle.JingleDescriptionController;
|
import org.jivesoftware.smackx.jingle.JingleDescriptionController;
|
||||||
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
|
import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransferFile;
|
||||||
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
|
import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener;
|
||||||
|
@ -31,5 +33,5 @@ public interface JingleFileTransferController extends JingleDescriptionControlle
|
||||||
|
|
||||||
JingleFileTransferFile getFile();
|
JingleFileTransferFile getFile();
|
||||||
|
|
||||||
void cancel();
|
void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,10 +94,10 @@ public final class JingleManager extends Manager {
|
||||||
// We have not seen this session before.
|
// We have not seen this session before.
|
||||||
// Either it is fresh, or unknown.
|
// Either it is fresh, or unknown.
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
LOGGER.log(Level.INFO, connection().getUser().asFullJidOrThrow() + " received unknown session: " + jingle.getFrom().asFullJidOrThrow() + " " + jingle.getSid());
|
|
||||||
if (jingle.getAction() == JingleAction.session_initiate) {
|
if (jingle.getAction() == JingleAction.session_initiate) {
|
||||||
//fresh. phew!
|
//fresh. phew!
|
||||||
try {
|
try {
|
||||||
|
LOGGER.log(Level.INFO, "Create new session with " + jingle.getFrom() + ": " + jingle.getSid());
|
||||||
session = JingleSession.fromSessionInitiate(JingleManager.this, jingle);
|
session = JingleSession.fromSessionInitiate(JingleManager.this, jingle);
|
||||||
jingleSessions.put(fullJidAndSessionId, session);
|
jingleSessions.put(fullJidAndSessionId, session);
|
||||||
} catch (UnsupportedDescriptionException e) {
|
} catch (UnsupportedDescriptionException e) {
|
||||||
|
@ -113,6 +113,7 @@ public final class JingleManager extends Manager {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Unknown session. Error!
|
// Unknown session. Error!
|
||||||
|
LOGGER.log(Level.INFO, connection().getUser().asFullJidOrThrow() + " received unknown session: " + jingle.getFrom().asFullJidOrThrow() + " " + jingle.getSid());
|
||||||
return JingleElement.createJingleErrorUnknownSession(jingle);
|
return JingleElement.createJingleErrorUnknownSession(jingle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,6 +178,7 @@ public class JingleContent implements JingleTransportCallback, JingleSecurityCal
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleContentRemove(JingleSession session, XMPPConnection connection) {
|
public void handleContentRemove(JingleSession session, XMPPConnection connection) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQ handleSecurityInfo(JingleElement request, XMPPConnection connection) {
|
private IQ handleSecurityInfo(JingleElement request, XMPPConnection connection) {
|
||||||
|
@ -210,17 +211,22 @@ public class JingleContent implements JingleTransportCallback, JingleSecurityCal
|
||||||
return transport.handleTransportInfo(content.getTransport().getInfo(), request);
|
return transport.handleTransportInfo(content.getTransport().getInfo(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQ handleTransportReject(JingleElement request, XMPPConnection connection) {
|
private IQ handleTransportReject(JingleElement request, final XMPPConnection connection) {
|
||||||
if (pendingReplacingTransport == null) {
|
if (pendingReplacingTransport == null) {
|
||||||
throw new AssertionError("We didn't try to replace the transport.");
|
throw new AssertionError("We didn't try to replace the transport.");
|
||||||
}
|
}
|
||||||
transportBlacklist.add(pendingReplacingTransport.getNamespace());
|
Async.go(new Runnable() {
|
||||||
pendingReplacingTransport = null;
|
@Override
|
||||||
try {
|
public void run() {
|
||||||
replaceTransport(transportBlacklist, connection);
|
transportBlacklist.add(pendingReplacingTransport.getNamespace());
|
||||||
} catch (SmackException.NotConnectedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | InterruptedException e) {
|
pendingReplacingTransport = null;
|
||||||
LOGGER.log(Level.SEVERE, "Could not replace transport: " + e, e);
|
try {
|
||||||
}
|
replaceTransport(transportBlacklist, connection);
|
||||||
|
} catch (SmackException.NotConnectedException | SmackException.NoResponseException | XMPPException.XMPPErrorException | InterruptedException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Could not replace transport: " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return IQ.createResultIQ(request);
|
return IQ.createResultIQ(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +466,20 @@ public class JingleContent implements JingleTransportCallback, JingleSecurityCal
|
||||||
LOGGER.log(Level.SEVERE, "Security failed: " + e, e);
|
LOGGER.log(Level.SEVERE, "Security failed: " + e, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onContentFinished() {
|
||||||
|
JingleSession session = getParent();
|
||||||
|
session.onContentFinished(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onContentFailed(Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onContentCancel() {
|
||||||
|
JingleSession session = getParent();
|
||||||
|
session.onContentCancel(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void replaceTransport(Set<String> blacklist, XMPPConnection connection)
|
private void replaceTransport(Set<String> blacklist, XMPPConnection connection)
|
||||||
throws SmackException.NotConnectedException, InterruptedException,
|
throws SmackException.NotConnectedException, InterruptedException,
|
||||||
XMPPException.XMPPErrorException, SmackException.NoResponseException {
|
XMPPException.XMPPErrorException, SmackException.NoResponseException {
|
||||||
|
|
|
@ -145,6 +145,58 @@ public class JingleSession {
|
||||||
return JingleElement.createSessionAccept(getInitiator(), getResponder(), getSessionId(), contentElements);
|
return JingleElement.createSessionAccept(getInitiator(), getResponder(), getSessionId(), contentElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onContentFinished(JingleContent jingleContent) {
|
||||||
|
if (contents.get(jingleContent.getName()) == null) {
|
||||||
|
LOGGER.log(Level.WARNING, "Session does not contain content " + jingleContent.getName() + ". Ignore contentFinished.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents.size() == 1) {
|
||||||
|
//Only content has finished. End session.
|
||||||
|
terminateSession(JingleReasonElement.Reason.success);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session has still active contents left.
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
jingleManager.getConnection().createStanzaCollectorAndSend(JingleElement.createSessionTerminateContentCancel(
|
||||||
|
getPeer(), getSessionId(), jingleContent.getCreator(), jingleContent.getName()));
|
||||||
|
} catch (SmackException.NotConnectedException | InterruptedException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Could not send content-cancel: " + e, e);
|
||||||
|
}
|
||||||
|
contents.remove(jingleContent.getName());
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void onContentCancel(JingleContent jingleContent) {
|
||||||
|
if (contents.get(jingleContent.getName()) == null) {
|
||||||
|
LOGGER.log(Level.WARNING, "Session does not contain content " + jingleContent.getName() + ". Ignore onContentCancel.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents.size() == 1) {
|
||||||
|
terminateSession(JingleReasonElement.Reason.cancel);
|
||||||
|
jingleManager.removeSession(this);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
jingleManager.getConnection().createStanzaCollectorAndSend(JingleElement.createSessionTerminateContentCancel(getPeer(), getSessionId(), jingleContent.getCreator(), jingleContent.getName()));
|
||||||
|
} catch (SmackException.NotConnectedException | InterruptedException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Could not send content-cancel: " + e, e);
|
||||||
|
}
|
||||||
|
contents.remove(jingleContent.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminateSession(JingleReasonElement.Reason reason) {
|
||||||
|
try {
|
||||||
|
jingleManager.getConnection().createStanzaCollectorAndSend(JingleElement.createSessionTerminate(getPeer(), getSessionId(), reason));
|
||||||
|
} catch (SmackException.NotConnectedException | InterruptedException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Could not send session-terminate: " + e, e);
|
||||||
|
}
|
||||||
|
jingleManager.removeSession(this);
|
||||||
|
}
|
||||||
|
|
||||||
public IQ handleJingleRequest(JingleElement request) {
|
public IQ handleJingleRequest(JingleElement request) {
|
||||||
switch (request.getAction()) {
|
switch (request.getAction()) {
|
||||||
case content_modify:
|
case content_modify:
|
||||||
|
@ -198,17 +250,27 @@ public class JingleSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQ handleSessionInitiate(JingleElement request) {
|
private IQ handleSessionInitiate(JingleElement request) {
|
||||||
JingleDescription<?> description = getSoleContentOrThrow().getDescription();
|
final JingleDescription<?> description = getSoleContentOrThrow().getDescription();
|
||||||
final JingleDescriptionManager descriptionManager = jingleManager.getDescriptionManager(description.getNamespace());
|
final JingleDescriptionManager descriptionManager = jingleManager.getDescriptionManager(description.getNamespace());
|
||||||
|
|
||||||
if (descriptionManager == null) {
|
if (descriptionManager == null) {
|
||||||
LOGGER.log(Level.WARNING, "Unsupported description type: " + description.getNamespace());
|
|
||||||
return JingleElement.createSessionTerminate(getPeer(), getSessionId(), JingleReasonElement.Reason.unsupported_applications);
|
|
||||||
}
|
}
|
||||||
Async.go(new Runnable() {
|
Async.go(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
descriptionManager.notifySessionInitiate(JingleSession.this);
|
if (descriptionManager == null) {
|
||||||
|
|
||||||
|
LOGGER.log(Level.WARNING, "Unsupported description type: " + description.getNamespace());
|
||||||
|
try {
|
||||||
|
jingleManager.getConnection().createStanzaCollectorAndSend(JingleElement.createSessionTerminate(getPeer(), getSessionId(), JingleReasonElement.Reason.unsupported_applications));
|
||||||
|
} catch (SmackException.NotConnectedException | InterruptedException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Could not send session-terminate: " + e, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
descriptionManager.notifySessionInitiate(JingleSession.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue