/**
* $RCSfile$
* $Revision: 2495 $
* $Date: 2005-05-30 10:14:25 -0500 (Mon, 30 May 2005) $
*
* Copyright 2003-2004 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.smackx.filetransfer;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smackx.packet.*;
import java.io.*;
import java.util.*;
import java.security.*;
/**
* Manages sending and receiving files.
* <br>
* Socks5 and IBB are supported.<br>
* Socks5 bytestreams are preferred over IBB because they are faster and create less
* traffic on the server.
* <br><br>
* An example for receiving a file:<br>
* <pre>
*
* FileTransferManager ft = new FileTransferManager(connection);
* ft.addFileReceiveListener(new ReceiveListener()
* {
* public void receiveFile(FileReceive r)
* {
* try {
* r.save(new File(r.getName()));
* }
* catch(XMPPException e)
* {
* // error receiving file
* }
* catch(IOException e)
* {
* // error saving file
* }
*
* // Optionally, you can reject the file by using r.reject();
* }
* } );
*
* </pre>
* <br>
* And an example sending a file:
* <br>
* <pre>
* FileTransferManager ft = new FileTransferManager(connection);
* try {
* ft.getFileSend(new File("somefile"), "This is a nice file").send("user@host");
* }
* catch(XMPPException e) {
* // error sending file
* }
* catch(IOException e)
* {
* // error reading file
* }
* </pre>
*/
public class FileTransferManager {
protected XMPPConnection connection;
private ArrayList receiveListeners = new ArrayList();
private int timeout = 90000;
private ArrayList hosts = new ArrayList();
/**
* Used when sending via Socks5
*/
public static final int TYPE_SOCKS5 = 1;
/**
* Used when sending via IBB
*/
public static final int TYPE_IBB = 2;
private int preferred = -1;
/**
* Creates an instance of FileTransferManager.
*
* @param connection The connection associated with this manager
*/
public FileTransferManager(XMPPConnection connection) {
this.setConnection(connection);
PacketFilter filter = new PacketTypeFilter(StreamInitiation.class);
connection.addPacketListener(new InitiationListener(), filter);
}
/**
* Sets the timeout for file transfers. Default is 90 seconds.
* We don't use Smack's packet timeout settings because file transfers
* usually require higher timeout values
* @param timeout The time, in milleseconds, before it times out
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* Returns the preferred type, or -1 if it doesn't matter
* @return the preferred type
*/
public int getPreferredType() { return preferred; }
/**
* Sets the preferred transfer type
* @param type The preferred transfer type, or -1 if it doesn't matter
*/
public void setPreferredType(int type) { this.preferred = type; }
/**
* Adds a possible streamhost to be use for bytestreams
*
*@param host The new streamhost in the format "host:port"
*/
public synchronized void addStreamHost(String host) {
hosts.add(host);
}
/**
* Clears the current proxy hosts
*/
public void clearHosts()
{
hosts.clear();
}
/**
* Removes a streamhost
*
*@param host The host to remove
*/
public synchronized void removeStreamHost(String host) {
for(int i = 0; i < hosts.size(); i++)
{
String h = (String)hosts.get(i);
if(host.equals(h))
{
hosts.remove(i);
break;
}
}
}
/**
* Returns a list of useable streamhosts
*@return ArrayList of useable streamhosts
*/
public synchronized ArrayList getStreamHosts() { return hosts; }
public int getTimeout() { return timeout; }
/**
* Adds a receive listener that will be called when a file request is received
*
* @param listener a listener interested in receiving file requests
*/
public void addFileReceiveListener(FileReceiveListener listener) {
receiveListeners.add(listener);
}
/**
* Returns the SHA-1 digest of a given string
*@param string The string to hash
*@return the SHA-1 hash of the given string
*/
public static String getDigest(String string) {
MessageDigest sha=null;
try {
sha = MessageDigest.getInstance("SHA");
sha.update(string.getBytes());
} catch (NoSuchAlgorithmException e) {
}
return stringValue(sha.digest());
}
/**
* Returns a HEX String representation of a byte array
*@param bytes The bytes to convert
*@return the converted String
*/
private static String stringValue(byte[] bytes) {
StringBuffer b = new StringBuffer(bytes.length * 2);
for(int i = 0; i < bytes.length; i++) {
int h = bytes[i];
if (h < 0) h = 256 + h;
if (h >=16) b.append(Integer.toHexString(h));
else {
b.append('0');
b.append(Integer.toHexString(h));
}
}
return b.toString().toLowerCase();
}
/**
* Listens for stream initiation events
* @author Adam Olsen
*/
class InitiationListener extends StreamInitiationListener {
public void processPacket(Packet packet) {
StreamInitiation si = (StreamInitiation) packet;
StreamInitiation.Feature feature = si.getFeature();
if(!feature.providesIBBOption() && !feature.providesBytestreamOption()) {
StreamInitiation iq = si.createConfirmationMessage(preferred);
iq.setType(IQ.Type.ERROR);
iq.setError(new XMPPError(501));
connection.sendPacket(iq);
return;
}
if (si.getType() == IQ.Type.SET) {
FileReceive receive = new FileReceive(FileTransferManager.this, si);
for (int i = 0; i < receiveListeners.size(); i++) {
FileReceiveListener l = (FileReceiveListener) receiveListeners.get(i);
l.receiveFile(receive);
}
}
}
}
/**
* Returns a FileSend request instance
*
* @param file the file you want to send
* @param desc the description of the file
*/
public FileSend getFileSend(File file, String desc)
throws IOException, XMPPException {
FileSend send = new FileSend(this, file, desc);
return send;
}
public FileSend getFileSend(InputStream stream, String name,
String desc, long size)
throws IOException, XMPPException {
FileSend send = new FileSend(this, stream, name, desc, size);
return send;
}
/**
* Sends progress updates to all interested listeners
*
*@param event The type of event
*@param listeners the interested listeners
*@param percent percent complete
*@param bytes Number of bytes transferred
*/
static void update(String event, ArrayList listeners, double percent, long bytes) {
for (int i = 0; i < listeners.size(); i++) {
FileProgressListener listener = (FileProgressListener) listeners.get(i);
listener.update(event, (int)(percent*100), bytes);
}
}
/**
* Various events that can be sent to progress listeners
*/
public static class Event {
/**
* Used when the connection is being made
*/
public final static String CONNECTING = "Connecting";
/**
* Used when data is being transferred
*/
public final static String TRANSFERRING = "Transferring";
/**
* Used when the transfer has completed
*/
public final static String DONE = "Done";
/**
* Used when an error occurs during transfer or connect
*/
public final static String ERROR = "Error";
/**
* Used when the transfer has been cancelled
*/
public final static String CANCELLED = "Cancelled";
}
protected XMPPConnection getConnection() {
return connection;
}
protected void setConnection(XMPPConnection connection) {
this.connection = connection;
}
}