diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java index 7b96cdbd4..353321dc6 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferManager.java @@ -16,28 +16,201 @@ */ package org.jivesoftware.smackx.jingle_filetransfer; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.jingle.JingleDescriptionManager; +import org.jivesoftware.smackx.jingle.JingleManager; +import org.jivesoftware.smackx.jingle.JingleTransportManager; +import org.jivesoftware.smackx.jingle.component.JingleContent; +import org.jivesoftware.smackx.jingle.component.JingleSession; +import org.jivesoftware.smackx.jingle.component.JingleTransport; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransport; +import org.jivesoftware.smackx.jingle.util.Role; +import org.jivesoftware.smackx.jingle_filetransfer.adapter.JingleFileTransferAdapter; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleOutgoingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileRequestController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileRequestListener; +import org.jivesoftware.smackx.jingle_filetransfer.provider.JingleFileTransferProvider; + +import org.jxmpp.jid.FullJid; /** - * Manager for JingleFileTransfer (XEP-0234). + * Manager for XEP-0234 - JingleFileTransfers. */ -public final class JingleFileTransferManager extends Manager { +public final class JingleFileTransferManager extends Manager implements JingleDescriptionManager { + + private static final Logger LOGGER = Logger.getLogger(JingleFileTransferManager.class.getName()); private static final WeakHashMap INSTANCES = new WeakHashMap<>(); + /** + * Reference to the JingleManager of the connection. + */ + private final JingleManager jingleManager; + + /** + * Listeners for incoming file offers. + */ + private final List offerListeners = + Collections.synchronizedList(new ArrayList()); + + /** + * Listeners for incoming file requests. + */ + private final List requestListeners = + Collections.synchronizedList(new ArrayList()); + + static { + // Register adapters and providers. + JingleManager.addJingleDescriptionAdapter(new JingleFileTransferAdapter()); + JingleManager.addJingleDescriptionProvider(new JingleFileTransferProvider()); + } + private JingleFileTransferManager(XMPPConnection connection) { super(connection); + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(getNamespace()); + jingleManager = JingleManager.getInstanceFor(connection); + jingleManager.addJingleDescriptionManager(this); } public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) { JingleFileTransferManager manager = INSTANCES.get(connection); + if (manager == null) { manager = new JingleFileTransferManager(connection); INSTANCES.put(connection, manager); } + return manager; } + + public OutgoingFileOfferController sendFile(File file, FullJid to) + throws SmackException.NotConnectedException, InterruptedException, XMPPException.XMPPErrorException, + SmackException.NoResponseException, SmackException.FeatureNotSupportedException, IOException, NoSuchAlgorithmException { + return sendFile(file, JingleFile.fromFile(file, null, null, null), to); + } + + public OutgoingFileOfferController sendFile(File file, JingleFile metadata, FullJid to) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, FileNotFoundException { + if (file == null || !file.exists()) { + throw new IllegalArgumentException("File MUST NOT be null and MUST exist."); + } + + FileInputStream fi = new FileInputStream(file); + + return sendStream(fi, metadata, to); + } + + public OutgoingFileOfferController sendStream(final InputStream stream, JingleFile metadata, FullJid recipient) throws SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { + if (!ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(recipient, getNamespace())) { + throw new SmackException.FeatureNotSupportedException(getNamespace(), recipient); + } + + JingleSession session = jingleManager.createSession(Role.initiator, recipient); + + JingleContent content = new JingleContent(JingleContentElement.Creator.initiator, JingleContentElement.Senders.initiator); + session.addContent(content); + + JingleOutgoingFileOffer outgoingFileOffer = new JingleOutgoingFileOffer(stream, metadata); + + content.setDescription(outgoingFileOffer); + + JingleTransportManager transportManager = jingleManager.getBestAvailableTransportManager(recipient); + if (transportManager == null) { + // At least Jingle IBB should be supported. + throw new SmackException.FeatureNotSupportedException(JingleIBBTransport.NAMESPACE, recipient); + } + JingleTransport transport = transportManager.createTransportForInitiator(content); + content.setTransport(transport); + + session.sendInitiate(connection()); + + return outgoingFileOffer; + } + + public OutgoingFileRequestController requestFile(JingleFile metadata, FullJid from) { + JingleOutgoingFileRequest request = new JingleOutgoingFileRequest(metadata); + + // TODO at some point. + + return request; + } + + public void addIncomingFileOfferListener(IncomingFileOfferListener listener) { + offerListeners.add(listener); + } + + public void removeIncomingFileOfferListener(IncomingFileOfferListener listener) { + offerListeners.remove(listener); + } + + public void notifyIncomingFileOfferListeners(JingleIncomingFileOffer offer) { + LOGGER.log(Level.FINE, "Incoming File transfer: [" + offer.getNamespace() + ", " + + offer.getParent().getTransport().getNamespace() + ", " + + (offer.getParent().getSecurity() != null ? offer.getParent().getSecurity().getNamespace() : "") + "]"); + for (IncomingFileOfferListener l : offerListeners) { + l.onIncomingFileOffer(offer); + } + } + + public void addIncomingFileRequestListener(IncomingFileRequestListener listener) { + requestListeners.add(listener); + } + + public void removeIncomingFileRequestListener(IncomingFileRequestListener listener) { + requestListeners.remove(listener); + } + + public void notifyIncomingFileRequestListeners(JingleIncomingFileRequest request) { + for (IncomingFileRequestListener l : requestListeners) { + l.onIncomingFileRequest(request); + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } + + private void notifyTransfer(JingleFileTransfer transfer) { + if (transfer.isOffer()) { + notifyIncomingFileOfferListeners((JingleIncomingFileOffer) transfer); + } else { + notifyIncomingFileRequestListeners((JingleIncomingFileRequest) transfer); + } + } + + @Override + public void notifySessionInitiate(JingleSession session) { + JingleContent content = session.getSoleContentOrThrow(); + notifyTransfer((JingleFileTransfer) content.getDescription()); + } + + @Override + public void notifyContentAdd(JingleSession session, JingleContent content) { + notifyTransfer((JingleFileTransfer) content.getDescription()); + } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java new file mode 100644 index 000000000..adc49716c --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/JingleFileTransferAdapter.java @@ -0,0 +1,65 @@ +/** + * + * 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.adapter; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smackx.jingle.adapter.JingleDescriptionAdapter; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionElement; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileRequest; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; + +/** + * Adapter to convert JingleFileTransferElements (element) to JingleFileTransfer objects (component). + */ +public class JingleFileTransferAdapter implements JingleDescriptionAdapter { + + private static final Logger LOGGER = Logger.getLogger(JingleFileTransferAdapter.class.getName()); + + @Override + public JingleFileTransfer descriptionFromElement(JingleContentElement.Creator creator, JingleContentElement.Senders senders, + String contentName, String contentDisposition, JingleContentDescriptionElement element) { + JingleFileTransferElement description = (JingleFileTransferElement) element; + List children = description.getJingleContentDescriptionChildren(); + assert children.size() == 1; + JingleFileTransferChildElement file = (JingleFileTransferChildElement) children.get(0); + + if (senders == JingleContentElement.Senders.initiator) { + return new JingleIncomingFileOffer(file); + } else if (senders == JingleContentElement.Senders.responder) { + return new JingleIncomingFileRequest(file); + } else { + if (senders == null) { + LOGGER.log(Level.INFO, "Senders is null. Gajim workaround: assume 'initiator'."); + return new JingleIncomingFileOffer(file); + } + throw new AssertionError("Senders attribute MUST be either initiator or responder. Is: " + senders); + } + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/package-info.java new file mode 100644 index 000000000..3cf5e866f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/adapter/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XEP-0234: Jingle File Transfer. + * Adapters. + */ +package org.jivesoftware.smackx.jingle_filetransfer.adapter; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java new file mode 100644 index 000000000..a29f31eb9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileOffer.java @@ -0,0 +1,27 @@ +/** + * + * 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.component; + +/** + * This class represents the base of a JingleFileOffer. + */ +public abstract class AbstractJingleFileOffer extends JingleFileTransfer { + + AbstractJingleFileOffer(JingleFile fileTransferFile) { + super(fileTransferFile); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java new file mode 100644 index 000000000..4cabd236b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/AbstractJingleFileRequest.java @@ -0,0 +1,28 @@ +/** + * + * 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.component; + +/** + * Class representing the base of a JingleFileRequest. + * TODO: Implement. + */ +public abstract class AbstractJingleFileRequest extends JingleFileTransfer { + + AbstractJingleFileRequest(JingleFile fileTransferFile) { + super(fileTransferFile); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java new file mode 100644 index 000000000..525cf6f66 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFile.java @@ -0,0 +1,150 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Date; + +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +/** + * Represent a file sent in a file transfer. This class contains metadata of the transferred file. + */ +public class JingleFile { + + private String name, description, mediaType; + private long size; + private Date date; + private HashElement hashElement; + + public static JingleFile fromFile(File file, String description, String mediaType, HashManager.ALGORITHM hashAlgorithm) throws NoSuchAlgorithmException, IOException { + + HashElement hashElement = null; + if (hashAlgorithm != null) { + hashElement = calculateHash(file, hashAlgorithm); + } + + return new JingleFile(file.getName(), description, file.length(), mediaType, new Date(file.lastModified()), hashElement); + } + + public JingleFile(String name, String description, long size, String mediaType, Date date, HashElement hashElement) { + this.name = name; + this.description = description; + this.size = size; + this.mediaType = mediaType; + this.date = date; + this.hashElement = hashElement; + } + + public JingleFile(JingleFileTransferChildElement element) { + this.name = element.getName(); + this.description = element.getDescription(); + this.size = element.getSize(); + this.mediaType = element.getMediaType(); + this.date = element.getDate(); + this.hashElement = element.getHash(); + } + + public static HashElement calculateHash(File file, HashManager.ALGORITHM algorithm) throws NoSuchAlgorithmException, IOException { + if (file == null || !file.exists()) { + throw new IllegalArgumentException("File MUST NOT be null and MUST exist."); + } + + MessageDigest digest = HashManager.getMessageDigest(algorithm); + if (digest == null) { + throw new NoSuchAlgorithmException("No algorithm for " + algorithm + " found."); + } + + FileInputStream fi = new FileInputStream(file); + DigestInputStream di = new DigestInputStream(fi, digest); + + while (di.available() > 0) { + di.read(); + } + + byte[] d = di.getMessageDigest().digest(); + + return new HashElement(algorithm, d); + } + + public JingleFileTransferChildElement getElement() { + JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder(); + builder.setDate(getDate()); + builder.setSize(getSize()); + builder.setName(getName()); + builder.setDescription(getDescription()); + builder.setMediaType(getMediaType()); + builder.setHash(getHashElement()); + + return builder.build(); + } + + public Date getDate() { + return date; + } + + public long getSize() { + return size; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getMediaType() { + return mediaType; + } + + public HashElement getHashElement() { + return hashElement; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setMediaType(String mediaType) { + this.mediaType = mediaType; + } + + public void setSize(long size) { + this.size = size; + } + + public void setDate(Date date) { + this.date = date; + } + + public void setHashElement(HashElement hashElement) { + this.hashElement = hashElement; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java new file mode 100644 index 000000000..985f95908 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleFileTransfer.java @@ -0,0 +1,140 @@ +/** + * + * 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.component; + +import java.util.ArrayList; +import java.util.Collections; +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.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.JingleFileTransferController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +/** + * Base class of a Jingle File Transfer. + */ +public abstract class JingleFileTransfer extends JingleDescription implements JingleFileTransferController { + + public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5"; + public static final String NAMESPACE = NAMESPACE_V5; + + protected State state; + protected JingleFile metadata; + protected float percentage; + + private final List progressListeners = Collections.synchronizedList(new ArrayList()); + + /** + * Create a new JingleFileTransfer. + * @param metadata metadata of the transferred file. + */ + JingleFileTransfer(JingleFile metadata) { + this.metadata = metadata; + } + + /** + * Is this a file offer? + * @return file offer? + */ + public abstract boolean isOffer(); + + /** + * Is this a file request? + * @return file request? + */ + public abstract boolean isRequest(); + + @Override + public void addProgressListener(ProgressListener listener) { + progressListeners.add(listener); + // TODO: Notify new listener? + } + + @Override + public void removeProgressListener(ProgressListener listener) { + progressListeners.remove(listener); + } + + @Override + public void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException { + getParent().onContentCancel(); + state = State.cancelled; + } + + /** + * Notify ProgressListeners, that the transmission has started. This happens after the negotiation is complete, + * when the transports are ready. + */ + public void notifyProgressListenersStarted() { + for (ProgressListener p : progressListeners) { + p.started(); + } + } + + /** + * Notify ProgressListeners, that the transmission has been terminated. This might happen at all times during the + * lifetime of the session. + * @param reason reason of termination. + */ + public void notifyProgressListenersTerminated(JingleReasonElement.Reason reason) { + for (ProgressListener p : progressListeners) { + p.terminated(reason); + } + } + + /** + * Return progress as a float value between 0 and 1. + * If the transmission has not yet started, return -1. + * @return -1 or percentage in [0,1] + */ + public float getPercentage() { + if (state == State.pending || state == State.negotiating) { + return -1f; + } + + return percentage; + } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } + + @Override + public void handleContentTerminate(JingleReasonElement.Reason reason) { + notifyProgressListenersTerminated(reason); + } + + @Override + public JingleFileTransferElement getElement() { + return new JingleFileTransferElement(metadata.getElement()); + } + + @Override + public State getState() { + return state; + } + + @Override + public JingleFile getMetadata() { + return metadata; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java new file mode 100644 index 000000000..387a5689f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileOffer.java @@ -0,0 +1,185 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +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.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +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.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +/** + * Behind the scenes logic of an incoming Jingle file offer (They offer a file). + */ +public class JingleIncomingFileOffer extends AbstractJingleFileOffer implements IncomingFileOfferController { + + private static final Logger LOGGER = Logger.getLogger(JingleIncomingFileOffer.class.getName()); + private OutputStream target; + + public JingleIncomingFileOffer(JingleFileTransferChildElement offer) { + super(new JingleFile(offer)); + this.state = State.pending; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + if (target == null) { + throw new IllegalStateException("Target OutputStream is null"); + } + + if (state == State.negotiating) { + state = State.active; + notifyProgressListenersStarted(); + } else { + return; + } + + HashElement hashElement = metadata.getHashElement(); + MessageDigest digest = null; + if (hashElement != null) { + digest = HashManager.getMessageDigest(hashElement.getAlgorithm()); + LOGGER.log(Level.FINE, "File offer had checksum: " + digest.toString() + ": " + hashElement.getHashB64()); + } + + LOGGER.log(Level.FINE, "Receive file"); + + InputStream inputStream = null; + try { + inputStream = bytestreamSession.getInputStream(); + + if (digest != null) { + inputStream = new DigestInputStream(inputStream, digest); + } + + int length = 0; + int read = 0; + byte[] bufbuf = new byte[4096]; + while ((length = inputStream.read(bufbuf)) >= 0) { + if (getState() == State.cancelled) { + break; + } + target.write(bufbuf, 0, length); + read += length; + LOGGER.log(Level.FINER, "Read " + read + " (" + length + ") of " + metadata.getSize() + " bytes."); + + percentage = ((float) read) / ((float) metadata.getSize()); + + if (read == (int) metadata.getSize()) { + break; + } + } + LOGGER.log(Level.FINE, "Reading/Writing finished."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Cannot get InputStream from BytestreamSession.", e); + } finally { + state = State.ended; + if (inputStream != null) { + try { + inputStream.close(); + LOGGER.log(Level.FINER, "CipherInputStream closed."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close InputStream.", e); + } + } + + if (target != null) { + try { + target.close(); + LOGGER.log(Level.FINER, "FileOutputStream closed."); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close OutputStream.", e); + } + } + } + + 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.FINE, "CHECKSUM MATCHED :)"); + } + } + notifyProgressListenersTerminated(JingleReasonElement.Reason.success); + getParent().onContentFinished(); + } + + @Override + public boolean isOffer() { + return true; + } + + @Override + public boolean isRequest() { + return false; + } + + @Override + public void accept(XMPPConnection connection, File target) + throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + SmackException.NoResponseException, IOException { + state = State.negotiating; + + if (!target.exists()) { + target.createNewFile(); + } + + this.target = new FileOutputStream(target); + + JingleSession session = getParent().getParent(); + if (session.getSessionState() == JingleSession.SessionState.pending) { + session.sendAccept(connection); + } + } + + @Override + public void accept(XMPPConnection connection, OutputStream stream) + throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + SmackException.NoResponseException { + state = State.negotiating; + + target = stream; + + JingleSession session = getParent().getParent(); + if (session.getSessionState() == JingleSession.SessionState.pending) { + session.sendAccept(connection); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java new file mode 100644 index 000000000..02286344c --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleIncomingFileRequest.java @@ -0,0 +1,60 @@ +/** + * + * 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.component; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileRequestController; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; + +/** + * Backend logic of an incoming file request (They request a file). + * TODO: Implement. + */ +public class JingleIncomingFileRequest extends AbstractJingleFileRequest implements IncomingFileRequestController { + + public JingleIncomingFileRequest(JingleFileTransferChildElement request) { + super(new JingleFile(request)); + } + + @Override + public JingleFileTransferElement getElement() { + return null; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public boolean isOffer() { + return false; + } + + @Override + public boolean isRequest() { + return true; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java new file mode 100644 index 000000000..10a71f5c6 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileOffer.java @@ -0,0 +1,117 @@ +/** + * + * 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.component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; + +/** + * Backend logic of an outgoing file offer (We offer a file). + */ +public class JingleOutgoingFileOffer extends AbstractJingleFileOffer implements OutgoingFileOfferController { + private static final Logger LOGGER = Logger.getLogger(JingleOutgoingFileOffer.class.getName()); + + private final InputStream source; + + public JingleOutgoingFileOffer(File file, JingleFile metadata) throws FileNotFoundException { + super(metadata); + this.source = new FileInputStream(file); + this.state = State.pending; + } + + public JingleOutgoingFileOffer(InputStream inputStream, JingleFile metadata) { + super(metadata); + this.source = inputStream; + this.state = State.pending; + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + if (source == null) { + throw new IllegalStateException("Source InputStream is null!"); + } + + state = State.active; + + notifyProgressListenersStarted(); + + OutputStream outputStream = null; + + try { + outputStream = bytestreamSession.getOutputStream(); + + byte[] buf = new byte[8192]; + + int written = 0; + + while (true) { + if (getState() == State.cancelled) { + break; + } + int r = source.read(buf); + if (r < 0) { + break; + } + outputStream.write(buf, 0, r); + written += r; + percentage = ((float) getMetadata().getSize()) / ((float) written); + } + + outputStream.flush(); + outputStream.close(); + + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Exception while sending file.", e); + } finally { + state = State.ended; + try { + source.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Could not close FileInputStream.", e); + } + } + + notifyProgressListenersTerminated(JingleReasonElement.Reason.success); + } + + @Override + public boolean isOffer() { + return true; + } + + @Override + public boolean isRequest() { + return false; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java new file mode 100644 index 000000000..b437aae6d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/JingleOutgoingFileRequest.java @@ -0,0 +1,52 @@ +/** + * + * 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.component; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionInfoElement; +import org.jivesoftware.smackx.jingle.element.JingleElement; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileRequestController; + +/** + * Backend logic of an outgoing file request (We request a file). + */ +public class JingleOutgoingFileRequest extends AbstractJingleFileRequest implements OutgoingFileRequestController { + + public JingleOutgoingFileRequest(JingleFile file) { + super(file); + } + + @Override + public JingleElement handleDescriptionInfo(JingleContentDescriptionInfoElement info) { + return null; + } + + @Override + public boolean isOffer() { + return false; + } + + @Override + public boolean isRequest() { + return true; + } + + @Override + public void onBytestreamReady(BytestreamSession bytestreamSession) { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/package-info.java new file mode 100644 index 000000000..beda2d84e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/component/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XEP-0234: Jingle File Transfer. + * Internal classes. + */ +package org.jivesoftware.smackx.jingle_filetransfer.component; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java new file mode 100644 index 000000000..75279fe29 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileOfferController.java @@ -0,0 +1,55 @@ +/** + * + * 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.controller; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; + +/** + * Interface with methods of an incoming file offer, that are exposed to the user. + */ +public interface IncomingFileOfferController extends JingleFileTransferController { + + /** + * Accept an incoming file offer. + * @param connection connection. + * @param target file to save the incoming data to. + * @throws InterruptedException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + * @throws IOException + */ + void accept(XMPPConnection connection, File target) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException; + + /** + * Accept an incoming file offer. + * @param connection connection. + * @param outputStream outputStream, to stream the incoming data to. + * @throws InterruptedException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + * @throws IOException + */ + void accept(XMPPConnection connection, OutputStream outputStream) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException; +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java new file mode 100644 index 000000000..14fbefc6e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/IncomingFileRequestController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Interface with methods of an incoming file request, that are exposed to the user. + */ +public interface IncomingFileRequestController extends JingleFileTransferController { + // TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java new file mode 100644 index 000000000..dfbcd139d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/JingleFileTransferController.java @@ -0,0 +1,55 @@ +/** + * + * 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.controller; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.jingle.JingleDescriptionController; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +/** + * Interface with methods of a jingle file transfer, that are exposed to the user. + */ +public interface JingleFileTransferController extends JingleDescriptionController { + + /** + * Add a ProgressListener. + * @param listener listener + */ + void addProgressListener(ProgressListener listener); + + /** + * Remove a ProgressListener. + * @param listener listener + */ + void removeProgressListener(ProgressListener listener); + + /** + * Get the JingleFile object containing metadata about the transferred file. + * @return metadata + */ + JingleFile getMetadata(); + + /** + * Actively cancel the file transfer. + * @param connection connection + * @throws SmackException.NotConnectedException + * @throws InterruptedException + */ + void cancel(XMPPConnection connection) throws SmackException.NotConnectedException, InterruptedException; +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java new file mode 100644 index 000000000..384c30a1f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileOfferController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Interface with methods of an outgoing file offer, that are exposed to the user. + */ +public interface OutgoingFileOfferController extends JingleFileTransferController { + // TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java new file mode 100644 index 000000000..a97aa8937 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/OutgoingFileRequestController.java @@ -0,0 +1,24 @@ +/** + * + * 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.controller; + +/** + * Interface with methods of an outgoing file request, that are exposed to the user. + */ +public interface OutgoingFileRequestController extends JingleFileTransferController { + // TODO: Declare methods. +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/package-info.java new file mode 100644 index 000000000..b2711989f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/controller/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XEP-0234: Jingle File Transfer. + * Controller. + */ +package org.jivesoftware.smackx.jingle_filetransfer.controller; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java similarity index 68% rename from smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java index 9f252bc23..19bb6dd78 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Checksum.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/ChecksumElement.java @@ -19,21 +19,29 @@ package org.jivesoftware.smackx.jingle_filetransfer.element; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.XmlStringBuilder; -import org.jivesoftware.smackx.jingle.element.JingleContent; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; /** - * Checksum element. + * Checksum element declared in XEP-0234. */ -public class Checksum implements ExtensionElement { +public class ChecksumElement implements ExtensionElement { + public static final String ELEMENT = "checksum"; public static final String ATTR_CREATOR = "creator"; public static final String ATTR_NAME = "name"; - private final JingleContent.Creator creator; + private final JingleContentElement.Creator creator; private final String name; - private final JingleFileTransferChild file; + private final JingleFileTransferChildElement file; - public Checksum(JingleContent.Creator creator, String name, JingleFileTransferChild file) { + /** + * Create a new ChecksumElement. + * @param creator creator of the content (party that added the file to the transmission). + * @param name name of the content. + * @param file metadata of the file. + */ + public ChecksumElement(JingleContentElement.Creator creator, String name, JingleFileTransferChildElement file) { this.creator = creator; this.name = name; this.file = Objects.requireNonNull(file, "file MUST NOT be null."); @@ -58,6 +66,6 @@ public class Checksum implements ExtensionElement { @Override public String getNamespace() { - return JingleFileTransfer.NAMESPACE_V5; + return JingleFileTransfer.NAMESPACE; } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java similarity index 65% rename from smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java index 6308266ac..0ace0def9 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChild.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferChildElement.java @@ -24,9 +24,10 @@ import org.jivesoftware.smackx.hashes.element.HashElement; import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; /** - * Content of type File. + * Metadata about the transferred file. */ -public class JingleFileTransferChild extends JingleContentDescriptionChildElement { +public class JingleFileTransferChildElement extends JingleContentDescriptionChildElement { + public static final String ELEMENT = "file"; public static final String ELEM_DATE = "date"; public static final String ELEM_DESC = "desc"; @@ -39,10 +40,20 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen private final HashElement hash; private final String mediaType; private final String name; - private final int size; + private final long size; private final Range range; - public JingleFileTransferChild(Date date, String desc, HashElement hash, String mediaType, String name, int size, Range range) { + /** + * Create a new JingleFileTransferChildElement. + * @param date last-modified date of the file. + * @param desc description of the file. + * @param hash hash value of the file (see XEP-0300). + * @param mediaType mediaType (https://www.iana.org/assignments/media-types/media-types.xhtml). + * @param name name of the file. + * @param size size of the file in bytes. + * @param range range of the transfer (see https://xmpp.org/extensions/xep-0234.html#range). + */ + public JingleFileTransferChildElement(Date date, String desc, HashElement hash, String mediaType, String name, long size, Range range) { this.date = date; this.desc = desc; this.hash = hash; @@ -52,30 +63,58 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen this.range = range; } + /** + * Return the last-modified date of the file. + * @return date. + */ public Date getDate() { return date; } + /** + * Return the description of the file. + * @return description. + */ public String getDescription() { return desc; } + /** + * Return the hash of the file. + * @return hash. + */ public HashElement getHash() { return hash; } + /** + * Return the mediaType of the file (https://www.iana.org/assignments/media-types/media-types.xhtml). + * @return media-type. + */ public String getMediaType() { return mediaType; } + /** + * Return the name of the file. + * @return filename. + */ public String getName() { return name; } - public int getSize() { + /** + * Return the size of the file in bytes. + * @return size. + */ + public long getSize() { return size; } + /** + * In case of a ranged transfer: Return the range of the transmission. Otherwise return null. + * @return range of the transfer. + */ public Range getRange() { return range; } @@ -96,7 +135,7 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen sb.optElement(ELEM_NAME, name); sb.optElement(range); if (size > 0) { - sb.element(ELEM_SIZE, Integer.toString(size)); + sb.element(ELEM_SIZE, Long.toString(size)); } sb.optElement(hash); sb.closeElement(this); @@ -113,7 +152,7 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen private HashElement hash; private String mediaType; private String name; - private int size; + private long size; private Range range; private Builder() { @@ -134,6 +173,14 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen return this; } + /** + * Set the media type of the file. + * This is a MIME type from this list: + * https://www.iana.org/assignments/media-types/media-types.xhtml + * Default should be application/octet-stream. + * @param mediaType new media type. + * @return builder. + */ public Builder setMediaType(String mediaType) { this.mediaType = mediaType; return this; @@ -144,7 +191,7 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen return this; } - public Builder setSize(int size) { + public Builder setSize(long size) { this.size = size; return this; } @@ -154,8 +201,8 @@ public class JingleFileTransferChild extends JingleContentDescriptionChildElemen return this; } - public JingleFileTransferChild build() { - return new JingleFileTransferChild(date, desc, hash, mediaType, name, size, range); + public JingleFileTransferChildElement build() { + return new JingleFileTransferChildElement(date, desc, hash, mediaType, name, size, range); } public Builder setFile(File file) { diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java similarity index 58% rename from smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java rename to smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java index 40dc1aaa2..ef0fa55fe 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransfer.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/JingleFileTransferElement.java @@ -16,23 +16,31 @@ */ package org.jivesoftware.smackx.jingle_filetransfer.element; +import java.util.Collections; import java.util.List; -import org.jivesoftware.smackx.jingle.element.JingleContentDescription; import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; +import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionElement; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; /** - * File element. + * Jingle File Transfer Element. */ -public class JingleFileTransfer extends JingleContentDescription { - public static final String NAMESPACE_V5 = "urn:xmpp:jingle:apps:file-transfer:5"; +public class JingleFileTransferElement extends JingleContentDescriptionElement { - public JingleFileTransfer(List payloads) { + public JingleFileTransferElement(JingleContentDescriptionChildElement payload) { + this(Collections.singletonList(payload)); + } + + public JingleFileTransferElement(List payloads) { super(payloads); + if (payloads.size() != 1) { + throw new IllegalArgumentException("Jingle File Transfers only support one payload element."); + } } @Override public String getNamespace() { - return NAMESPACE_V5; + return JingleFileTransfer.NAMESPACE; } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java index db64d0466..ae0565da4 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/element/Range.java @@ -29,22 +29,22 @@ public class Range implements NamedElement { public static final String ATTR_OFFSET = "offset"; public static final String ATTR_LENGTH = "length"; - private final int offset, length; + private final Long offset, length; private final HashElement hash; /** * Create a Range element with default values. */ public Range() { - this(0, -1, null); + this(null, null, null); } /** * Create a Range element with specified length. * @param length length of the transmitted data in bytes. */ - public Range(int length) { - this(0, length, null); + public Range(Long length) { + this(null, length, null); } /** @@ -52,7 +52,7 @@ public class Range implements NamedElement { * @param offset offset in bytes from the beginning of the transmitted data. * @param length number of bytes that shall be transferred. */ - public Range(int offset, int length) { + public Range(Long offset, Long length) { this(offset, length, null); } @@ -62,7 +62,7 @@ public class Range implements NamedElement { * @param length number of bytes that shall be transferred. * @param hash hash of the bytes in the specified range. */ - public Range(int offset, int length, HashElement hash) { + public Range(Long offset, Long length, HashElement hash) { this.offset = offset; this.length = length; this.hash = hash; @@ -73,7 +73,7 @@ public class Range implements NamedElement { * This marks the begin of the specified range. * @return offset */ - public int getOffset() { + public Long getOffset() { return offset; } @@ -81,7 +81,7 @@ public class Range implements NamedElement { * Return the length of the range. * @return length */ - public int getLength() { + public Long getLength() { return length; } @@ -102,12 +102,8 @@ public class Range implements NamedElement { public CharSequence toXML(String enclosingNamespace) { XmlStringBuilder sb = new XmlStringBuilder(this); - if (offset > 0) { - sb.attribute(ATTR_OFFSET, offset); - } - if (length > 0) { - sb.attribute(ATTR_LENGTH, length); - } + sb.optAttribute(ATTR_OFFSET, offset); + sb.optAttribute(ATTR_LENGTH, length); if (hash != null) { sb.rightAngleBracket(); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java new file mode 100644 index 000000000..3316e1e11 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileOfferListener.java @@ -0,0 +1,31 @@ +/** + * + * 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.listener; + +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; + +/** + * Listener for incoming file offers. + */ +public interface IncomingFileOfferListener { + + /** + * Notify client of an incoming file offer. + * @param offer offer. + */ + void onIncomingFileOffer(IncomingFileOfferController offer); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java new file mode 100644 index 000000000..68dfac11c --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/IncomingFileRequestListener.java @@ -0,0 +1,31 @@ +/** + * + * 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.listener; + +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileRequestController; + +/** + * Listener for incoming file requests. + */ +public interface IncomingFileRequestListener { + + /** + * Notify the client of an incoming file request. + * @param request request. + */ + void onIncomingFileRequest(IncomingFileRequestController request); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java new file mode 100644 index 000000000..6ca2b2736 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/ProgressListener.java @@ -0,0 +1,36 @@ +/** + * + * 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.listener; + +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; + +/** + * ProgressListener that listens for progress changes of a file transfer. + */ +public interface ProgressListener { + + /** + * Bytestream transmission has stared. This usually happens after the negotiation is finished. + */ + void started(); + + /** + * Transfer has been terminated. This might happen at all times. + * @param reason reason (eg. cancel). + */ + void terminated(JingleReasonElement.Reason reason); +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/package-info.java new file mode 100644 index 000000000..438b31b61 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/listener/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ + +/** + * Smack's API for XEP-0234: Jingle File Transfer. + * Listeners. + */ +package org.jivesoftware.smackx.jingle_filetransfer.listener; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java index 980b50a74..f8f63b6e4 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/ChecksumProvider.java @@ -20,50 +20,53 @@ import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smackx.hashes.element.HashElement; import org.jivesoftware.smackx.hashes.provider.HashElementProvider; -import org.jivesoftware.smackx.jingle.element.JingleContent; -import org.jivesoftware.smackx.jingle_filetransfer.element.Checksum; -import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChild; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.ChecksumElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; import org.jivesoftware.smackx.jingle_filetransfer.element.Range; import org.xmlpull.v1.XmlPullParser; + /** * Provider for the Checksum element. */ -public class ChecksumProvider extends ExtensionElementProvider { +public class ChecksumProvider extends ExtensionElementProvider { + + private static final HashElementProvider hashProvider = new HashElementProvider(); + @Override - public Checksum parse(XmlPullParser parser, int initialDepth) throws Exception { - JingleContent.Creator creator = null; - String creatorString = parser.getAttributeValue(null, Checksum.ATTR_CREATOR); + public ChecksumElement parse(XmlPullParser parser, int initialDepth) throws Exception { + JingleContentElement.Creator creator = null; + String creatorString = parser.getAttributeValue(null, ChecksumElement.ATTR_CREATOR); if (creatorString != null) { - creator = JingleContent.Creator.valueOf(creatorString); + creator = JingleContentElement.Creator.valueOf(creatorString); } - String name = parser.getAttributeValue(null, Checksum.ATTR_NAME); + String name = parser.getAttributeValue(null, ChecksumElement.ATTR_NAME); - JingleFileTransferChild.Builder cb = JingleFileTransferChild.getBuilder(); + JingleFileTransferChildElement.Builder cb = JingleFileTransferChildElement.getBuilder(); HashElement hashElement = null; Range range = null; boolean go = true; while (go) { int tag = parser.nextTag(); - String n = parser.getText(); + String n = parser.getName(); if (tag == START_TAG) { switch (n) { case HashElement.ELEMENT: - hashElement = new HashElementProvider().parse(parser); + hashElement = hashProvider.parse(parser); break; case Range.ELEMENT: - String offset = parser.getAttributeValue(null, Range.ATTR_OFFSET); - String length = parser.getAttributeValue(null, Range.ATTR_LENGTH); - int o = offset == null ? 0 : Integer.parseInt(offset); - int l = length == null ? -1 : Integer.parseInt(length); - range = new Range(o, l); + Long offset = ParserUtils.getLongAttribute(parser, Range.ATTR_OFFSET); + Long length = ParserUtils.getLongAttribute(parser, Range.ATTR_LENGTH); + range = new Range(offset, length); } } else if (tag == END_TAG) { switch (n) { @@ -74,7 +77,7 @@ public class ChecksumProvider extends ExtensionElementProvider { } break; - case JingleFileTransferChild.ELEMENT: + case JingleFileTransferChildElement.ELEMENT: if (hashElement != null) { cb.setHash(hashElement); } @@ -85,6 +88,6 @@ public class ChecksumProvider extends ExtensionElementProvider { } } } - return new Checksum(creator, name, cb.build()); + return new ChecksumElement(creator, name, cb.build()); } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java index 8570188a6..1076b8d38 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/provider/JingleFileTransferProvider.java @@ -21,12 +21,14 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import java.util.ArrayList; +import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smackx.hashes.element.HashElement; import org.jivesoftware.smackx.hashes.provider.HashElementProvider; import org.jivesoftware.smackx.jingle.element.JingleContentDescriptionChildElement; import org.jivesoftware.smackx.jingle.provider.JingleContentDescriptionProvider; -import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransfer; -import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChild; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFileTransfer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferElement; import org.jivesoftware.smackx.jingle_filetransfer.element.Range; import org.jxmpp.util.XmppDateTime; @@ -36,18 +38,15 @@ import org.xmlpull.v1.XmlPullParser; * Provider for JingleContentDescriptionFileTransfer elements. */ public class JingleFileTransferProvider - extends JingleContentDescriptionProvider { + extends JingleContentDescriptionProvider { @Override - public JingleFileTransfer parse(XmlPullParser parser, int initialDepth) throws Exception { + public JingleFileTransferElement parse(XmlPullParser parser, int initialDepth) throws Exception { ArrayList payloads = new ArrayList<>(); boolean inRange = false; - JingleFileTransferChild.Builder builder = JingleFileTransferChild.getBuilder(); + JingleFileTransferChildElement.Builder builder = JingleFileTransferChildElement.getBuilder(); HashElement inRangeHash = null; - - int offset = 0; - int length = -1; - + Long length = null, offset = null; while (true) { int tag = parser.nextTag(); @@ -55,32 +54,30 @@ public class JingleFileTransferProvider if (tag == START_TAG) { switch (elem) { - case JingleFileTransferChild.ELEM_DATE: + case JingleFileTransferChildElement.ELEM_DATE: builder.setDate(XmppDateTime.parseXEP0082Date(parser.nextText())); break; - case JingleFileTransferChild.ELEM_DESC: + case JingleFileTransferChildElement.ELEM_DESC: builder.setDescription(parser.nextText()); break; - case JingleFileTransferChild.ELEM_MEDIA_TYPE: + case JingleFileTransferChildElement.ELEM_MEDIA_TYPE: builder.setMediaType(parser.nextText()); break; - case JingleFileTransferChild.ELEM_NAME: + case JingleFileTransferChildElement.ELEM_NAME: builder.setName(parser.nextText()); break; - case JingleFileTransferChild.ELEM_SIZE: + case JingleFileTransferChildElement.ELEM_SIZE: builder.setSize(Integer.parseInt(parser.nextText())); break; case Range.ELEMENT: inRange = true; - String offsetString = parser.getAttributeValue(null, Range.ATTR_OFFSET); - String lengthString = parser.getAttributeValue(null, Range.ATTR_LENGTH); - offset = (offsetString != null ? Integer.parseInt(offsetString) : 0); - length = (lengthString != null ? Integer.parseInt(lengthString) : -1); + offset = ParserUtils.getLongAttribute(parser, Range.ATTR_OFFSET); + length = ParserUtils.getLongAttribute(parser, Range.ATTR_LENGTH); if (parser.isEmptyElementTag()) { inRange = false; @@ -106,15 +103,20 @@ public class JingleFileTransferProvider inRangeHash = null; break; - case JingleFileTransferChild.ELEMENT: + case JingleFileTransferChildElement.ELEMENT: payloads.add(builder.build()); - builder = JingleFileTransferChild.getBuilder(); + builder = JingleFileTransferChildElement.getBuilder(); break; - case JingleFileTransfer.ELEMENT: - return new JingleFileTransfer(payloads); + case JingleFileTransferElement.ELEMENT: + return new JingleFileTransferElement(payloads); } } } } + + @Override + public String getNamespace() { + return JingleFileTransfer.NAMESPACE; + } } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java new file mode 100644 index 000000000..690d0fbfd --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/ChecksumTest.java @@ -0,0 +1,67 @@ +/** + * + * 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 static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.hashes.HashManager; +import org.jivesoftware.smackx.hashes.element.HashElement; +import org.jivesoftware.smackx.jingle.element.JingleContentElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.ChecksumElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; +import org.jivesoftware.smackx.jingle_filetransfer.element.Range; +import org.jivesoftware.smackx.jingle_filetransfer.provider.ChecksumProvider; + +import org.junit.Test; + +/** + * Created by vanitas on 12.07.17. + */ +public class ChecksumTest extends SmackTestSuite { + + @Test + public void parserTest() throws Exception { + HashElement hash = new HashElement(HashManager.ALGORITHM.SHA_256, "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk="); + JingleFileTransferChildElement file = new JingleFileTransferChildElement(null, null, hash, null, null, -1, null); + ChecksumElement checksum = new ChecksumElement(JingleContentElement.Creator.initiator, "name", file); + + String xml = "" + + "" + + "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" + + "" + + ""; + + assertXMLEqual(xml, checksum.toXML(null).toString()); + assertXMLEqual(xml, new ChecksumProvider().parse(TestUtils.getParser(xml)).toXML(null).toString()); + + Range range = new Range(12L,34L); + file = new JingleFileTransferChildElement(null, null, hash, null, null, -1, range); + checksum = new ChecksumElement(JingleContentElement.Creator.initiator, "name", file); + + xml = "" + + "" + + "" + + "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk=" + + "" + + ""; + assertXMLEqual(xml, checksum.toXML(null).toString()); + assertXMLEqual(xml, new ChecksumProvider().parse(TestUtils.getParser(xml)).toXML(null).toString()); + + } +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.java new file mode 100644 index 000000000..17e14966b --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/jingle_filetransfer/IncomingFileTransferTest.java @@ -0,0 +1,40 @@ +/** + * + * 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 static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + +import java.util.Date; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleIncomingFileOffer; +import org.jivesoftware.smackx.jingle_filetransfer.element.JingleFileTransferChildElement; + +import org.junit.Test; + +public class IncomingFileTransferTest extends SmackTestSuite { + + @Test + public void incomingFileOfferTest() { + Date date = new Date(); + JingleFileTransferChildElement offerElement = new JingleFileTransferChildElement(date, "description", null, "application/octet-stream", "name", 1234, null); + JingleIncomingFileOffer offer = new JingleIncomingFileOffer(offerElement); + assertTrue(offer.isOffer()); + assertFalse(offer.isRequest()); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferCancelIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferCancelIntegrationTest.java new file mode 100644 index 000000000..909965c83 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferCancelIntegrationTest.java @@ -0,0 +1,165 @@ +/** + * + * 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 static junit.framework.TestCase.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Random; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransport; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager; +import org.jivesoftware.smackx.jingle_filetransfer.component.JingleFile; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.BeforeClass; + +public class JingleFileTransferCancelIntegrationTest extends AbstractSmackIntegrationTest { + + public JingleFileTransferCancelIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @BeforeClass + public void setup() { + JingleFileTransferManager.getInstanceFor(conOne); + JingleIBBTransportManager.getInstanceFor(conOne); + ServiceDiscoveryManager.getInstanceFor(conOne).addFeature(JingleIBBTransport.NAMESPACE); + JingleFileTransferManager.getInstanceFor(conThree); + JingleIBBTransportManager.getInstanceFor(conThree); + ServiceDiscoveryManager.getInstanceFor(conThree).addFeature(JingleIBBTransport.NAMESPACE); + } + + @SmackIntegrationTest + public void senderCancelTest() throws Exception { + final SimpleResultSyncPoint s1 = new SimpleResultSyncPoint(); + + byte[] payload = new byte[320000]; + new Random().nextBytes(payload); + + JingleFileTransferManager.getInstanceFor(conThree).addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(IncomingFileOfferController offer) { + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.cancel) { + s1.signal(); + } else { + s1.signalFailure(); + } + } + }); + try { + offer.accept(conThree, new ByteArrayOutputStream()); + } catch (InterruptedException | IOException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + fail(e.toString()); + } + } + }); + + final OutgoingFileOfferController out = JingleFileTransferManager.getInstanceFor(conOne).sendStream(new ByteArrayInputStream(payload), new JingleFile("name", null, 320000, null, null, null), conThree.getUser().asFullJidOrThrow()); + out.addProgressListener(new ProgressListener() { + @Override + public void started() { + try { + out.cancel(conOne); + } catch (SmackException.NotConnectedException | InterruptedException e) { + fail(e.toString()); + } + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + + } + }); + + s1.waitForResult(60 * 1000); + } + + @SmackIntegrationTest + public void receiverCancelTest() throws Exception { + final SimpleResultSyncPoint s1 = new SimpleResultSyncPoint(); + + byte[] payload = new byte[320000]; + new Random().nextBytes(payload); + + JingleFileTransferManager.getInstanceFor(conThree).addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(final IncomingFileOfferController offer) { + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + try { + offer.cancel(conThree); + } catch (SmackException.NotConnectedException | InterruptedException e) { + fail(e.toString()); + } + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + + } + }); + try { + offer.accept(conThree, new ByteArrayOutputStream()); + } catch (InterruptedException | IOException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { + fail(e.toString()); + } + } + }); + + final OutgoingFileOfferController out = JingleFileTransferManager.getInstanceFor(conOne).sendStream(new ByteArrayInputStream(payload), new JingleFile("name", null, 320000, null, null, null), conThree.getUser().asFullJidOrThrow()); + out.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.cancel) { + s1.signal(); + } else { + s1.signalFailure(); + } + } + }); + + s1.waitForResult(60 * 1000); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java new file mode 100644 index 000000000..07970bb35 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferIntegrationTest.java @@ -0,0 +1,165 @@ +/** + * + * 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 static junit.framework.TestCase.fail; +import static org.junit.Assert.assertArrayEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.AfterClass; +import org.jxmpp.jid.FullJid; + +/** + * Created by vanitas on 29.06.17. + */ +public class JingleFileTransferIntegrationTest extends AbstractSmackIntegrationTest { + + private static final File tempDir; + + static { + String userHome = System.getProperty("user.home"); + if (userHome != null) { + File f = new File(userHome); + tempDir = new File(f, ".config/smack-integration-test/"); + } else { + tempDir = new File("int_test_jingle"); + } + } + + public JingleFileTransferIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @SmackIntegrationTest + public void basicFileTransferTest() throws Exception { + JingleIBBTransportManager.getInstanceFor(conOne); + JingleIBBTransportManager.getInstanceFor(conTwo); + + + final SimpleResultSyncPoint resultSyncPoint1 = new SimpleResultSyncPoint(); + final SimpleResultSyncPoint resultSyncPoint2 = new SimpleResultSyncPoint(); + + FullJid alice = conOne.getUser().asFullJidOrThrow(); + FullJid bob = conTwo.getUser().asFullJidOrThrow(); + + File source = prepareNewTestFile("source"); + final File target = new File(tempDir, "target"); + + JingleFileTransferManager aftm = JingleFileTransferManager.getInstanceFor(conOne); + JingleFileTransferManager bftm = JingleFileTransferManager.getInstanceFor(conTwo); + + bftm.addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(IncomingFileOfferController offer) { + + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.success) { + resultSyncPoint2.signal(); + } + } + }); + + try { + offer.accept(conTwo, target); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException | IOException e) { + fail(e.toString()); + } + } + }); + + final OutgoingFileOfferController sending = aftm.sendFile(source, bob); + + sending.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.success) { + resultSyncPoint1.signal(); + } + } + }); + + resultSyncPoint1.waitForResult(60 * 1000); + resultSyncPoint2.waitForResult(60 * 1000); + + byte[] sBytes = new byte[(int) source.length()]; + byte[] tBytes = new byte[(int) target.length()]; + try { + FileInputStream fi = new FileInputStream(source); + fi.read(sBytes); + fi.close(); + fi = new FileInputStream(target); + fi.read(tBytes); + } catch (IOException e) { + fail("Could not read files: " + e.toString() + " " + e.getMessage()); + } + + assertArrayEquals(sBytes, tBytes); + + } + + public static File prepareNewTestFile(String name) { + File testFile = new File(tempDir, name); + try { + if (!testFile.exists()) { + testFile.createNewFile(); + } + FileOutputStream fo = new FileOutputStream(testFile); + byte[] rand = new byte[16000]; + INSECURE_RANDOM.nextBytes(rand); + fo.write(rand); + fo.close(); + return testFile; + } catch (IOException e) { + return null; + } + } + + @AfterClass + public static void cleanup() { + Socks5Proxy.getSocks5Proxy().stop(); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java new file mode 100644 index 000000000..76a7479d3 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/JingleFileTransferTransportFallbackIntegrationTest.java @@ -0,0 +1,182 @@ +/** + * + * 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 static junit.framework.TestCase.fail; +import static org.junit.Assert.assertArrayEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5Proxy; +import org.jivesoftware.smackx.jingle.element.JingleReasonElement; +import org.jivesoftware.smackx.jingle.transport.jingle_ibb.JingleIBBTransportManager; +import org.jivesoftware.smackx.jingle.transport.jingle_s5b.JingleS5BTransportManager; +import org.jivesoftware.smackx.jingle_filetransfer.controller.IncomingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.controller.OutgoingFileOfferController; +import org.jivesoftware.smackx.jingle_filetransfer.listener.IncomingFileOfferListener; +import org.jivesoftware.smackx.jingle_filetransfer.listener.ProgressListener; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.jxmpp.jid.FullJid; + +public class JingleFileTransferTransportFallbackIntegrationTest extends AbstractSmackIntegrationTest { + + private static final File tempDir; + + static { + String userHome = System.getProperty("user.home"); + if (userHome != null) { + File f = new File(userHome); + tempDir = new File(f, ".config/smack-integration-test/"); + } else { + tempDir = new File("int_test_jingle"); + } + } + + public JingleFileTransferTransportFallbackIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @Before + public void crippleS5B() { + // Manipulate the Manager so that it'll fail. + JingleS5BTransportManager.useExternalCandidates = false; + JingleS5BTransportManager.useLocalCandidates = false; + // *evil super villain laughter* + } + + @SmackIntegrationTest + public void S5BtoIBBfallbackTest() throws Exception { + + JingleS5BTransportManager.getInstanceFor(conOne); + JingleS5BTransportManager.getInstanceFor(conTwo); + + // Use Jingle IBB Transport as fallback. + JingleIBBTransportManager.getInstanceFor(conOne); + JingleIBBTransportManager.getInstanceFor(conTwo); + + + final SimpleResultSyncPoint resultSyncPoint1 = new SimpleResultSyncPoint(); + final SimpleResultSyncPoint resultSyncPoint2 = new SimpleResultSyncPoint(); + + FullJid alice = conOne.getUser().asFullJidOrThrow(); + FullJid bob = conTwo.getUser().asFullJidOrThrow(); + + File source = prepareNewTestFile("source"); + final File target = new File(tempDir, "target"); + + JingleFileTransferManager aftm = JingleFileTransferManager.getInstanceFor(conOne); + JingleFileTransferManager bftm = JingleFileTransferManager.getInstanceFor(conTwo); + + bftm.addIncomingFileOfferListener(new IncomingFileOfferListener() { + @Override + public void onIncomingFileOffer(IncomingFileOfferController offer) { + offer.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.success) { + resultSyncPoint2.signal(); + } + } + }); + + try { + offer.accept(conTwo, target); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException | IOException e) { + fail(e.toString()); + } + } + }); + + final OutgoingFileOfferController sending = aftm.sendFile(source, bob); + + sending.addProgressListener(new ProgressListener() { + @Override + public void started() { + + } + + @Override + public void terminated(JingleReasonElement.Reason reason) { + if (reason == JingleReasonElement.Reason.success) { + resultSyncPoint1.signal(); + } + } + }); + + resultSyncPoint1.waitForResult(60 * 1000); + resultSyncPoint2.waitForResult(60 * 1000); + + byte[] sBytes = new byte[(int) source.length()]; + byte[] tBytes = new byte[(int) target.length()]; + try { + FileInputStream fi = new FileInputStream(source); + fi.read(sBytes); + fi.close(); + fi = new FileInputStream(target); + fi.read(tBytes); + } catch (IOException e) { + fail("Could not read files: " + e.toString() + " " + e.getMessage()); + } + + assertArrayEquals(sBytes, tBytes); + } + + @After + public void cureS5B() { + JingleS5BTransportManager.useExternalCandidates = true; + JingleS5BTransportManager.useLocalCandidates = true; + } + + public static File prepareNewTestFile(String name) { + File testFile = new File(tempDir, name); + try { + if (!testFile.exists()) { + testFile.createNewFile(); + } + FileOutputStream fo = new FileOutputStream(testFile); + byte[] rand = new byte[16000]; + INSECURE_RANDOM.nextBytes(rand); + fo.write(rand); + fo.close(); + return testFile; + } catch (IOException e) { + return null; + } + } + + @AfterClass + public void cleanup() { + Socks5Proxy.getSocks5Proxy().stop(); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java new file mode 100644 index 000000000..1c0d4d68c --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/jingle_filetransfer/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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. + */ + +/** + * Tests for XEP-0234 - Jingle File Transfer. + */ +package org.jivesoftware.smackx.jingle_filetransfer;