Smack/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingJingleFileOffer.java

258 lines
11 KiB
Java

/**
*
* Copyright 2017 Paul Schaub
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.jingle_filetransfer;
import java.io.File;
import java.util.concurrent.Future;
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.smackx.bytestreams.BytestreamSession;
import org.jivesoftware.smackx.jingle.JingleManager;
import org.jivesoftware.smackx.jingle.JingleTransportMethodManager;
import org.jivesoftware.smackx.jingle.Role;
import org.jivesoftware.smackx.jingle3.element.JingleContentElement;
import org.jivesoftware.smackx.jingle3.element.JingleElement;
import org.jivesoftware.smackx.jingle3.element.JingleReasonElement;
import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationCallback;
import org.jivesoftware.smackx.jingle.transports.JingleTransportManager;
import org.jivesoftware.smackx.jingle_filetransfer.callback.IncomingFileOfferCallback;
import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer;
import org.jivesoftware.smackx.jingle_filetransfer.handler.FileTransferHandler;
import org.jxmpp.jid.FullJid;
/**
* We are the responder and we are the recipient.
*/
public class IncomingJingleFileOffer extends JingleFileTransferSession implements IncomingFileOfferCallback {
private static final Logger LOGGER = Logger.getLogger(IncomingJingleFileOffer.class.getName());
private JingleElement pendingSessionInitiate = null;
private ReceiveTask receivingThread;
private File target;
@Override
public void cancel() {
if (state == State.active) {
Future<?> task = queued.get(0);
if (task != null) {
task.cancel(true);
queued.remove(task);
}
notifyEndedListeners(JingleReasonElement.Reason.cancel);
}
}
public enum State {
fresh,
pending,
sent_transport_replace,
active,
terminated
}
private State state;
public IncomingJingleFileOffer(XMPPConnection connection, FullJid initiator, String sid) {
super(connection, initiator, connection.getUser().asFullJidOrThrow(), Role.responder, sid, Type.offer);
state = State.fresh;
}
public IncomingJingleFileOffer(XMPPConnection connection, JingleElement request) {
this(connection, request.getInitiator(), request.getSid());
}
@Override
public IQ handleSessionInitiate(final JingleElement initiate)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException {
JingleTransportMethodManager tm = JingleTransportMethodManager.getInstanceFor(connection);
if (state != State.fresh) {
//Out of order (initiate after accept)
LOGGER.log(Level.WARNING, "Action " + initiate.getAction() + " is out of order!");
return jutil.createErrorOutOfOrder(initiate);
}
this.contents.addAll(initiate.getContents());
this.file = (JingleFileTransfer) contents.get(0).getDescription();
JingleTransportManager<?> transportManager = tm.getTransportManager(initiate);
if (transportManager == null) {
//Try fallback.
pendingSessionInitiate = initiate;
transportManager = tm.getBestAvailableTransportManager();
if (transportManager == null) {
//No usable transports.
LOGGER.log(Level.WARNING, "No usable transports.");
connection.createStanzaCollectorAndSend(jutil.createSessionTerminateUnsupportedTransports(getInitiator(), getSessionId()));
state = State.terminated;
return jutil.createAck(initiate);
}
transportSession = transportManager.transportSession(this);
jutil.sendTransportReplace(initiate.getFrom().asFullJidOrThrow(), getInitiator(),
getSessionId(), contents.get(0).getCreator(), contents.get(0).getName(),
transportSession.createTransport());
state = State.sent_transport_replace;
return jutil.createAck(initiate);
}
transportSession = transportManager.transportSession(this);
transportSession.processJingle(initiate);
state = State.pending;
JingleFileTransferManager.getInstanceFor(connection).notifyIncomingFileOffer(initiate,
IncomingJingleFileOffer.this);
return jutil.createAck(initiate);
}
@Override
public IQ handleTransportReplace(final JingleElement transportReplace)
throws InterruptedException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException {
final JingleTransportManager<?> replacementManager = JingleTransportMethodManager.getInstanceFor(connection)
.getTransportManager(transportReplace);
queued.add(JingleManager.getThreadPool().submit(new Runnable() {
@Override
public void run() {
try {
if (replacementManager != null) {
LOGGER.log(Level.INFO, "Accept transport-replace.");
IncomingJingleFileOffer.this.transportSession = replacementManager.transportSession(IncomingJingleFileOffer.this);
transportSession.processJingle(transportReplace);
transportSession.initiateIncomingSession(new JingleTransportInitiationCallback() {
@Override
public void onSessionInitiated(BytestreamSession bytestreamSession) {
LOGGER.log(Level.INFO, "Bytestream initiated. Start receiving.");
receivingThread = new ReceiveTask(IncomingJingleFileOffer.this, bytestreamSession, file, target);
queued.add(JingleManager.getThreadPool().submit(receivingThread));
}
@Override
public void onException(Exception e) {
LOGGER.log(Level.SEVERE, "EXCEPTION IN INCOMING SESSION: ", e);
}
});
jutil.sendTransportAccept(transportReplace.getFrom().asFullJidOrThrow(),
transportReplace.getInitiator(), transportReplace.getSid(),
getContents().get(0).getCreator(), getContents().get(0).getName(),
transportSession.createTransport());
} else {
LOGGER.log(Level.INFO, "Unsupported transport. Reject transport-replace.");
jutil.sendTransportReject(transportReplace.getFrom().asFullJidOrThrow(), transportReplace.getInitiator(),
transportReplace.getSid(), getContents().get(0).getCreator(),
getContents().get(0).getName(), transportReplace.getContents().get(0).getTransport());
}
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.SEVERE, "Help me please!", e);
}
}
}));
return jutil.createAck(transportReplace);
}
@Override
public IQ handleTransportAccept(JingleElement transportAccept) {
LOGGER.log(Level.INFO, "Received transport-accept.");
if (state != State.sent_transport_replace) {
LOGGER.log(Level.WARNING, "Session is in state " + state + ", so the transport-accept is out of order.");
return jutil.createErrorOutOfOrder(transportAccept);
}
JingleFileTransferManager.getInstanceFor(connection)
.notifyIncomingFileOffer(pendingSessionInitiate, this);
transportSession.processJingle(transportAccept);
state = State.pending;
return jutil.createAck(transportAccept);
}
@Override
public void onTransportMethodFailed(String namespace) {
//Nothing to do.
}
@Override
public FileTransferHandler acceptIncomingFileOffer(final JingleElement request, final File target) {
this.target = target;
LOGGER.log(Level.INFO, "Client accepted incoming file offer. Try to start receiving.");
if (transportSession == null) {
//Unsupported transport
LOGGER.log(Level.WARNING, "Unsupported Transport method.");
try {
jutil.sendSessionTerminateUnsupportedTransports(request.getFrom().asFullJidOrThrow(), sid);
} catch (InterruptedException | SmackException.NoResponseException |
SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
LOGGER.log(Level.SEVERE, "Could not send session-terminate: " + e, e);
}
return null;
}
state = State.active;
try {
jutil.sendSessionAccept(getInitiator(), sid, getContents().get(0).getCreator(),
getContents().get(0).getName(), JingleContentElement.Senders.initiator, file,
transportSession.createTransport());
} catch (SmackException.NotConnectedException | SmackException.NoResponseException |
XMPPException.XMPPErrorException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Could not send session-accept.", e);
}
transportSession.initiateIncomingSession(new JingleTransportInitiationCallback() {
@Override
public void onSessionInitiated(BytestreamSession bytestreamSession) {
LOGGER.log(Level.INFO, "Bytestream initiated. Start receiving.");
receivingThread = new ReceiveTask(IncomingJingleFileOffer.this, bytestreamSession, file, target);
queued.add(JingleManager.getThreadPool().submit(receivingThread));
started = true;
notifyStartedListeners();
}
@Override
public void onException(Exception e) {
LOGGER.log(Level.SEVERE, "EXCEPTION IN INCOMING SESSION: ", e);
}
});
return this;
}
@Override
public void declineIncomingFileOffer(JingleElement request) {
state = State.terminated;
try {
jutil.sendSessionTerminateDecline(request.getInitiator(), request.getSid());
} catch (SmackException.NotConnectedException | SmackException.NoResponseException |
XMPPException.XMPPErrorException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Could not send session-terminate: " + e, e);
}
}
}