Commit b66d5cbc authored by Guus der Kinderen's avatar Guus der Kinderen

OF-923: File Transfer should emit 'transfer completed' event.

- Reworked file transfer event handling
- Fixed spelling in API (as this change already broke API backwards compatibility).
- Updated ClientControl plugin to reflect these changes.
parent a897ab9f
......@@ -31,6 +31,8 @@ import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
......@@ -45,12 +47,13 @@ import java.util.List;
*/
public class DefaultFileTransferManager extends BasicModule implements FileTransferManager {
private static final Logger Log = LoggerFactory.getLogger( DefaultFileTransferManager.class );
private static final String CACHE_NAME = "File Transfer Cache";
private final Cache<String, FileTransfer> fileTransferMap;
private final List<FileTransferInterceptor> fileTransferInterceptorList
= new ArrayList<FileTransferInterceptor>();
private final List<FileTransferEventListener> eventListeners = new ArrayList<>();
/**
* Default constructor creates the cache.
......@@ -99,8 +102,8 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans
public boolean acceptIncomingFileTransferRequest(FileTransfer transfer)
throws FileTransferRejectedException
{
fireFileTransferIntercept(transfer, false);
if(transfer != null) {
fireFileTransferStart( transfer.getSessionID(), false );
String streamID = transfer.getSessionID();
JID from = new JID(transfer.getInitiator());
JID to = new JID(transfer.getTarget());
......@@ -152,25 +155,54 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans
return new FileTransfer(from.toString(), to.toString(), streamID, fileName, size, mimeType);
}
public void addFileTransferInterceptor(FileTransferInterceptor interceptor) {
fileTransferInterceptorList.add(interceptor);
@Override
public void addListener( FileTransferEventListener eventListener )
{
eventListeners.add( eventListener );
}
public void removeFileTransferInterceptor(FileTransferInterceptor interceptor) {
fileTransferInterceptorList.remove(interceptor);
@Override
public void removeListener( FileTransferEventListener eventListener )
{
eventListeners.remove( eventListener );
}
public void fireFileTransferIntercept(FileTransferProgress transfer, boolean isReady)
throws FileTransferRejectedException
@Override
public void fireFileTransferStart( String sid, boolean isReady ) throws FileTransferRejectedException
{
fireFileTransferIntercept(fileTransferMap.get(transfer.getSessionID()), isReady);
final FileTransfer transfer = fileTransferMap.get( sid );
for ( FileTransferEventListener listener : eventListeners )
{
try
{
listener.fileTransferStart( transfer, isReady );
}
catch ( FileTransferRejectedException ex )
{
Log.debug( "Listener '{}' rejected file transfer '{}'.", listener, transfer );
throw ex;
}
catch ( Exception ex )
{
Log.warn( "Listener '{}' threw exception when being informed of file transfer complete for transfer '{}'.", listener, transfer, ex );
}
}
}
private void fireFileTransferIntercept(FileTransfer transfer, boolean isReady)
throws FileTransferRejectedException
@Override
public void fireFileTransferCompleted( String sid, boolean wasSuccessful )
{
for(FileTransferInterceptor interceptor : fileTransferInterceptorList) {
interceptor.interceptFileTransfer(transfer, isReady);
final FileTransfer transfer = fileTransferMap.get( sid );
for ( FileTransferEventListener listener : eventListeners )
{
try
{
listener.fileTransferComplete( transfer, wasSuccessful );
}
catch ( Exception ex )
{
Log.warn( "Listener '{}' threw exception when being informed of file transfer complete for transfer '{}'.", listener, transfer, ex );
}
}
}
......@@ -182,7 +214,7 @@ public class DefaultFileTransferManager extends BasicModule implements FileTrans
boolean processed)
throws PacketRejectedException
{
// We only want packets recieved by the server
// We only want packets received by the server
if (!processed && incoming && packet instanceof IQ) {
IQ iq = (IQ) packet;
Element childElement = iq.getChildElement();
......
package org.jivesoftware.openfire.filetransfer;
/**
* An event listener for File Transfer related events.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public interface FileTransferEventListener
{
/**
* Invoked when a file transfer is about to start.. The interceptor can either modify the file transfer or
* throw a FileTransferRejectedException. The file transfer went sent to the interceptor can be in two states, ready
* and not ready. The not ready state indicates that this event was fired when the file transfer request was sent by
* the initiator. The ready state indicates that the file transfer is ready to begin, and the channels can be
* manipulated by the interceptor.
* <p/>
* It is recommended for the the sake of user experience that when in the not ready state, any processing done on
* the file transfer should be quick.
*
* @param transfer the transfer being intercepted (never null).
* @param isReady true if the transfer is ready to commence or false if this is related to the
* initial file transfer request. An exception at this point will cause the transfer to
* not go through.
*/
void fileTransferStart( FileTransfer transfer, boolean isReady ) throws FileTransferRejectedException;
/**
* Invoked when a file transfer was completed. Events are generated for events that succeeded, but also for those
* that failed.
*
* @param transfer the transfer being intercepted (never null).
* @param wasSuccessful false when an exception was thrown during file transfer, otherwise true.
*/
void fileTransferComplete( FileTransfer transfer, boolean wasSuccessful );
}
/**
* $RCSfile$
* $Revision: 3144 $
* $Date: 2005-12-01 14:20:11 -0300 (Thu, 01 Dec 2005) $
*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* 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.openfire.filetransfer;
/**
* Interface used to capture a file transfer before it begins.
*
* @author Alexander Wenckus
*/
public interface FileTransferInterceptor {
/**
* Invokes the interceptor on the specified file transfer. The interceptor can either modify
* the file transfer or throw a FileTransferRejectedException. The file transfer went sent to
* the interceptor can be in two states, ready and not ready. The not ready state indicates
* that this event was fired when the file transfer request was sent by the initatior. The ready
* state indicates that the file transfer is ready to begin, and the channels can be
* manipulated by the interceptor.
* <p>
* It is recommended for the the sake of user experience that
* when in the not ready state, any processing done on the file transfer should be quick.
*
* @param transfer the transfer being intercepted
* @param isReady true if the transfer is ready to commence or false if this is related to the
* initial file transfer request. An exception at this point will cause the transfer to
* not go through.
*
*/
void interceptFileTransfer(FileTransfer transfer, boolean isReady)
throws FileTransferRejectedException;
}
......@@ -67,10 +67,40 @@ public interface FileTransferManager extends Module {
void registerProxyTransfer(String transferDigest, ProxyTransfer proxyTransfer)
throws UnauthorizedException;
void addFileTransferInterceptor(FileTransferInterceptor interceptor);
/**
* Registers an event listener that will be notified of file transfer related events.
*
* @param eventListener an event listener (cannot be null).
*/
void addListener( FileTransferEventListener eventListener );
void removeFileTransferInterceptor(FileTransferInterceptor interceptor);
/**
* Unregisters an event listener from the list of event listeners that are notified of file transfer related events.
*
* @param eventListener an event listener (cannot be null).
*/
void removeListener( FileTransferEventListener eventListener );
void fireFileTransferIntercept(FileTransferProgress transfer, boolean isReady)
/**
* Invokes {@link FileTransferEventListener#fileTransferStart(FileTransfer, boolean)} for all registered event
* listeners.
*
* @param sid The session id of the file transfer that is being intercepted (cannot be null).
* @param isReady true if the transfer is ready to commence or false if this is related to the
* initial file transfer request. An exception at this point will cause the transfer to
* not go through.
* @throws FileTransferRejectedException When at least one of the listeners aborts the file transfer.
*/
void fireFileTransferStart( String sid, boolean isReady )
throws FileTransferRejectedException;
/**
* Invokes {@link FileTransferEventListener#fileTransferComplete(FileTransfer, boolean)} for all registered event
* listeners.
*
* @param sid The session id of the file transfer that is being intercepted (cannot be null).
* @param wasSuccessful false when an exception was thrown during file transfer, otherwise true.
* @throws FileTransferRejectedException When at least one of the listeners aborts the file transfer.
*/
void fireFileTransferCompleted( String sid, boolean wasSuccessful );
}
......@@ -30,7 +30,14 @@ import java.io.OutputStream;
* @author Alexander Wenckus
*/
public interface FileTransferProgress {
public long getAmountTransfered() throws UnsupportedOperationException;
/**
* Returns the number of bytes that has been transferred.
*
* @return the number of bytes that has been transferred.
* @throws UnsupportedOperationException
*/
public long getAmountTransferred() throws UnsupportedOperationException;
/**
* Returns the fully qualified JID of the initiator of the file transfer.
......
......@@ -23,7 +23,7 @@ import java.io.PrintStream;
import java.io.PrintWriter;
/**
* Thrown by a FileTransferInterceptor when a file transfer is rejected my the Interceptor. The file
* Thrown by a {@link FileTransferEventListener} when a file transfer is rejected by the Interceptor. The file
* transfer is aborted and the participating parties are notified.
*
* @author Alexander Wenckus
......@@ -82,7 +82,7 @@ public class FileTransferRejectedException extends Exception {
}
/**
* Retuns the text to include in a message that will be sent to the intitiator and target
* Returns the text to include in a message that will be sent to the intitiator and target
* of the file transfer that got rejected or <tt>null</tt> if none was defined. If no text was
* specified then no message will be sent to the parties of the rejected file transfer.
*
......
......@@ -117,7 +117,7 @@ public class DefaultProxyTransfer implements ProxyTransfer {
this.future = future;
}
public long getAmountTransfered() {
public long getAmountTransferred() {
return amountWritten;
}
......
......@@ -277,11 +277,10 @@ public class ProxyConnectionManager {
* packet after both parties have connected to the proxy.
*
* @param initiator The initiator or sender of the file transfer.
* @param target The target or reciever of the file transfer.
* @param sid The sessionid the uniquely identifies the transfer between
* the two participants.
* @param target The target or receiver of the file transfer.
* @param sid The session id that uniquely identifies the transfer between the two participants.
* @throws IllegalArgumentException This exception is thrown when the activated transfer does
* not exist or is missing one or both of the realted sockets.
* not exist or is missing one or both of the sockets.
*/
void activate(JID initiator, JID target, String sid) {
final String digest = createDigest(sid, initiator, target);
......@@ -303,7 +302,7 @@ public class ProxyConnectionManager {
transfer.setTransferFuture(executor.submit(new Runnable() {
public void run() {
try {
transferManager.fireFileTransferIntercept(transfer, true);
transferManager.fireFileTransferStart( transfer.getSessionID(), true );
}
catch (FileTransferRejectedException e) {
notifyFailure(transfer, e);
......@@ -311,9 +310,11 @@ public class ProxyConnectionManager {
}
try {
transfer.doTransfer();
transferManager.fireFileTransferCompleted( transfer.getSessionID(), true );
}
catch (IOException e) {
Log.error("Error during file transfer", e);
transferManager.fireFileTransferCompleted( transfer.getSessionID(), false );
}
finally {
connectionMap.remove(digest);
......@@ -331,7 +332,7 @@ public class ProxyConnectionManager {
* initiator + target).
*
* @param sessionID The sessionID of the stream negotiation
* @param initiator The inititator of the stream negotiation
* @param initiator The initiator of the stream negotiation
* @param target The target of the stream negotiation
* @return SHA-1 hash of the three parameters
*/
......@@ -374,7 +375,7 @@ public class ProxyConnectionManager {
}
public double sample() {
return (ProxyOutputStream.amountTransfered.getAndSet(0) / 1000d);
return (ProxyOutputStream.amountTransferred.getAndSet(0) / 1000d);
}
public boolean isPartialSample() {
......
......@@ -25,10 +25,10 @@ import java.io.DataOutputStream;
import java.util.concurrent.atomic.AtomicLong;
/**
* An output stream which tracks the amount of bytes transfered by proxy sockets.
* An output stream which tracks the amount of bytes transfered by proxy sockets.
*/
public class ProxyOutputStream extends DataOutputStream {
static AtomicLong amountTransfered = new AtomicLong(0);
static AtomicLong amountTransferred = new AtomicLong(0);
public ProxyOutputStream(OutputStream out) {
super(out);
......@@ -37,6 +37,6 @@ public class ProxyOutputStream extends DataOutputStream {
@Override
public synchronized void write(byte b[], int off, int len) throws IOException {
super.write(b, off, len);
amountTransfered.addAndGet(len);
amountTransferred.addAndGet(len);
}
}
......@@ -43,7 +43,12 @@
<h1>
Client Control Plugin Changelog
</h1>
<p><b>1.2.0</b> -- Sep 13, 2013</p>
<p><b>1.3.0</b> -- June 22, 2015</p>
<ul>
<li>Requires Openfire 3.11.0.</li>
</ul>
<p><b>1.2.0</b> -- September 13, 2013</p>
<ul>
<li>Requires Openfire 3.9.0.</li>
</ul>
......
......@@ -9,8 +9,8 @@
<description>Controls clients allowed to connect and available features</description>
<author>Jive Software</author>
<version>1.2.0</version>
<date>9/13/2013</date>
<minServerVersion>3.9.0</minServerVersion>
<date>6/22/2015</date>
<minServerVersion>3.11.0</minServerVersion>
<databaseKey>clientcontrol</databaseKey>
<databaseVersion>0</databaseVersion>
......
......@@ -50,11 +50,11 @@ public class FileTransferFilterManager {
}
public void start() {
manager.addFileTransferInterceptor(transferInterceptor);
manager.addListener(transferInterceptor);
}
public void stop() {
manager.removeFileTransferInterceptor(transferInterceptor);
manager.removeListener(transferInterceptor);
}
public void enableFileTransfer(boolean isEnabled) {
......@@ -66,16 +66,18 @@ public class FileTransferFilterManager {
DEFAULT_IS_FILE_TRANSFER_ENABLED);
}
private class TransferInterceptor implements FileTransferInterceptor {
public void interceptFileTransfer(FileTransfer transfer, boolean isReady)
throws FileTransferRejectedException
private class TransferInterceptor implements FileTransferEventListener
{
@Override
public void fileTransferStart( FileTransfer transfer, boolean isReady ) throws FileTransferRejectedException
{
if(!isFileTransferEnabled()) {
throw new FileTransferRejectedException();
}
}
@Override
public void fileTransferComplete( FileTransfer transfer, boolean wasSuccessful )
{}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment