/**
* $RCSfile: ,v $
* $Revision: $
* $Date: $
*
* Copyright (C) 2004-2011 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.spark.filetransfer;
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.jivesoftware.MainWindow;
import org.jivesoftware.Spark;
import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.filetransfer.FileTransferManager;
import org.jivesoftware.smackx.filetransfer.FileTransferRequest;
import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer;
import org.jivesoftware.spark.ChatManager;
import org.jivesoftware.spark.PresenceManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.filetransfer.preferences.FileTransferPreference;
import org.jivesoftware.spark.preference.PreferenceManager;
import org.jivesoftware.spark.ui.ChatFrame;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ChatRoomButton;
import org.jivesoftware.spark.ui.ChatRoomClosingListener;
import org.jivesoftware.spark.ui.ChatRoomListenerAdapter;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.ui.FileDropListener;
import org.jivesoftware.spark.ui.ImageSelectionPanel;
import org.jivesoftware.spark.ui.TranscriptWindow;
import org.jivesoftware.spark.ui.rooms.ChatRoomImpl;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.Downloads;
import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.ReceiveFileTransfer;
import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.SendFileTransfer;
import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.TransferUtils;
import org.jivesoftware.sparkimpl.plugin.manager.Enterprise;
/**
* Responsible for the handling of File Transfer within Spark. You would use the SparkManager
* for sending of images, files, multiple files and adding your own transfer listeners for plugin work.
*
* @author Derek DeMoro
*/
public class SparkTransferManager {
private List<FileTransferListener> listeners = new ArrayList<FileTransferListener>();
private File defaultDirectory;
private static SparkTransferManager singleton;
private static final Object LOCK = new Object();
private FileTransferManager transferManager;
private Map<String,ArrayList<File>> waitMap = new HashMap<String,ArrayList<File>>();
private BufferedImage bufferedImage;
private ImageSelectionPanel selectionPanel;
private Robot robot;
/**
* Returns the singleton instance of <CODE>SparkTransferManager</CODE>,
* creating it if necessary.
* <p/>
*
* @return the singleton instance of <Code>SparkTransferManager</CODE>
*/
public static SparkTransferManager getInstance() {
// Synchronize on LOCK to ensure that we don't end up creating
// two singletons.
synchronized (LOCK) {
if (null == singleton) {
SparkTransferManager controller = new SparkTransferManager();
singleton = controller;
return controller;
}
}
return singleton;
}
private SparkTransferManager() {
boolean enabled = Enterprise.containsFeature(Enterprise.FILE_TRANSFER_FEATURE);
if (!enabled) {
return;
}
SparkManager.getConnection().addConnectionListener(new ConnectionListener() {
public void connectionClosed() {
}
public void connectionClosedOnError(Exception e) {
}
public void reconnectingIn(int seconds) {
}
public void reconnectionSuccessful() {
// Re-create transfer manager.
transferManager = new FileTransferManager(SparkManager.getConnection());
}
public void reconnectionFailed(Exception e) {
}
});
try {
robot = new Robot();
selectionPanel = new ImageSelectionPanel();
}
catch (AWTException e) {
Log.error(e);
}
// Register Preferences
PreferenceManager prefManager = SparkManager.getPreferenceManager();
prefManager.addPreference(new FileTransferPreference());
final JMenu actionsMenu = SparkManager.getMainWindow().getMenuByName(Res.getString("menuitem.actions"));
JMenuItem downloadsMenu = new JMenuItem("", SparkRes.getImageIcon(SparkRes.DOWNLOAD_16x16));
ResourceUtils.resButton(downloadsMenu, Res.getString("menuitem.view.downloads"));
actionsMenu.addSeparator();
actionsMenu.add(downloadsMenu);
downloadsMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
launchFile(Downloads.getDownloadDirectory());
}
});
// Create the file transfer manager
transferManager = new FileTransferManager(SparkManager.getConnection());
final ContactList contactList = SparkManager.getWorkspace().getContactList();
// Create the listener
transferManager.addFileTransferListener(new org.jivesoftware.smackx.filetransfer.FileTransferListener() {
public void fileTransferRequest(final FileTransferRequest request) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
handleTransferRequest(request, contactList);
}
});
}
});
// Add Send File to Chat Room
addSendFileButton();
contactList.addFileDropListener(new FileDropListener() {
public void filesDropped(Collection<File> files, Component component) {
if (component instanceof ContactItem) {
ContactItem item = (ContactItem)component;
ChatRoom chatRoom = null;
for (File file : files) {
chatRoom = sendFile(file, item.getJID());
}
if (chatRoom != null) {
SparkManager.getChatManager().getChatContainer().activateChatRoom(chatRoom);
}
}
}
});
if (defaultDirectory == null) {
defaultDirectory = new File(System.getProperty("user.home"));
}
addPresenceListener();
// // Add View Downloads to Command Panel
// final JPanel commandPanel = SparkManager.getWorkspace().getCommandPanel();
//
// RolloverButton viewDownloads = new RolloverButton(SparkRes.getImageIcon(SparkRes.DOWNLOAD_16x16));
// viewDownloads.setToolTipText(Res.getString("menuitem.view.downloads"));
// commandPanel.add(viewDownloads);
// viewDownloads.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// try {
// Desktop.getDesktop().browse(Downloads.getDownloadDirectory().toURI());
// } catch (IOException e1) {
// Log.error("Could not find file-browser");
// }
// }
// });
}
/**
* Return correct URI for filePath. dont mind of local or remote path
*
* @param filePath
* @return
*/
private static URI getFileURI(String filePath) {
URI uri = null;
filePath = filePath.trim();
if (filePath.indexOf("http") == 0 || filePath.indexOf("\\") == 0) {
if (filePath.indexOf("\\") == 0)
filePath = "file:" + filePath;
try {
filePath = filePath.replaceAll(" ", "%20");
URL url = new URL(filePath);
uri = url.toURI();
} catch (MalformedURLException ex) {
ex.printStackTrace();
} catch (URISyntaxException ex) {
ex.printStackTrace();
}
} else {
File file = new File(filePath);
uri = file.toURI();
}
return uri;
}
private void handleTransferRequest(FileTransferRequest request, ContactList contactList) {
// Check if a listener handled this request
if (fireTransferListeners(request)) {
return;
}
String requestor = request.getRequestor();
String bareJID = StringUtils.parseBareAddress(requestor);
String fileName = request.getFileName();
ContactItem contactItem = contactList.getContactItemByJID(bareJID);
ChatRoom chatRoom;
if (contactItem != null) {
chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getDisplayName(), contactItem.getDisplayName());
}
else {
chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID);
}
TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow();
transcriptWindow.insertCustomText(Res.getString("message.file.transfer.chat.window"), true, false, Color.BLACK);
final ReceiveFileTransfer receivingMessageUI = new ReceiveFileTransfer();
receivingMessageUI.acceptFileTransfer(request);
chatRoom.addClosingListener(new ChatRoomClosingListener() {
public void closing() {
receivingMessageUI.cancelTransfer();
}
});
transcriptWindow.addComponent(receivingMessageUI);
chatRoom.increaseUnreadMessageCount();
chatRoom.scrollToBottom();
String fileTransMsg = contactItem.getDisplayName() + " " + Res.getString("message.file.transfer.short.message") + " " + fileName;
SparkManager.getChatManager().getChatContainer().fireNotifyOnMessage(chatRoom, true, fileTransMsg, Res.getString("message.file.transfer.notification"));
}
public void sendFileTo(ContactItem item) {
FileDialog fileChooser = getFileChooser(SparkManager.getMainWindow(), Res.getString("title.select.file.to.send"));
fileChooser.setVisible(true);
if (fileChooser.getDirectory() == null || fileChooser.getFile() == null) {
return;
}
File file = new File(fileChooser.getDirectory(), fileChooser.getFile());
if (file.exists()) {
defaultDirectory = file.getParentFile();
sendFile(file, item.getJID());
}
}
private void addSendFileButton() {
final ChatManager chatManager = SparkManager.getChatManager();
chatManager.addChatRoomListener(new ChatRoomListenerAdapter() {
public void chatRoomOpened(final ChatRoom room) {
if (!(room instanceof ChatRoomImpl)) {
return;
}
// Otherwise,
new ChatRoomTransferDecorator(room);
}
public void chatRoomClosed(ChatRoom room) {
}
});
}
public void sendScreenshot(final ChatRoomButton button, final ChatRoom room) {
button.setEnabled(false);
final MainWindow mainWindow = SparkManager.getMainWindow();
final ChatFrame chatFrame = SparkManager.getChatManager().getChatContainer().getChatFrame();
final boolean mainWindowVisible = mainWindow.isVisible();
final boolean chatFrameVisible = chatFrame.isVisible();
if (mainWindowVisible) {
mainWindow.setVisible(false);
}
if (chatFrameVisible) {
chatFrame.setVisible(false);
}
final SwingWorker worker = new SwingWorker() {
public Object construct() {
try {
Thread.sleep(1000);
Rectangle area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
return robot.createScreenCapture(area);
}
catch (Throwable e) {
Log.error(e);
if (mainWindowVisible) {
mainWindow.setVisible(true);
}
if (chatFrameVisible) {
chatFrame.setVisible(true);
}
}
return null;
}
public void finished() {
bufferedImage = (BufferedImage)get();
if (bufferedImage == null) {
JOptionPane.showMessageDialog(null, Res.getString("title.error"), "Unable to process screenshot.", JOptionPane.ERROR_MESSAGE);
return;
}
final Frame frame = new Frame();
frame.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
selectionPanel.setImage(bufferedImage);
selectionPanel.validate();
selectionPanel.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
Rectangle clip = selectionPanel.getClip();
BufferedImage newImage = null;
try {
newImage = bufferedImage.getSubimage((int)clip.getX(), (int)clip.getY(), (int)clip.getWidth(), (int)clip.getHeight());
}
catch (Exception e1) {
// Nothing to do
}
if (newImage != null) {
sendImage(newImage, room);
bufferedImage = null;
selectionPanel.clear();
}
frame.dispose();
frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
if (mainWindowVisible) {
mainWindow.setVisible(true);
}
if (chatFrameVisible) {
chatFrame.setVisible(true);
}
selectionPanel.removeMouseListener(this);
}
});
frame.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ESCAPE) {
frame.dispose();
frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
if (mainWindowVisible) {
mainWindow.setVisible(true);
}
if (chatFrameVisible) {
chatFrame.setVisible(true);
}
}
}
});
frame.setSize(bufferedImage.getWidth(null), bufferedImage.getHeight());
frame.add(selectionPanel);
frame.setUndecorated(true);
// Determine if full-screen mode is supported directly
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
if (gs.isFullScreenSupported()) {
gs.setFullScreenWindow(frame);
}
else {
// Full-screen mode will be simulated
frame.setVisible(true);
}
button.setEnabled(true);
}
};
worker.start();
}
private void addPresenceListener() {
SparkManager.getConnection().addPacketListener(new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
if (presence.isAvailable()) {
String bareJID = StringUtils.parseBareAddress(presence.getFrom());
// Iterate through map.
ArrayList<File> list = waitMap.get(bareJID);
if (list != null) {
// Iterate through list and send.
Iterator<File> iter = list.iterator();
ChatRoom room = null;
while (iter.hasNext()) {
File file = iter.next();
room = sendFile(file, bareJID);
}
if (room != null) {
Message message = new Message();
message.setBody(Res.getString("message.sent.offline.files"));
room.sendMessage(message);
}
}
waitMap.remove(bareJID);
}
}
}, new PacketTypeFilter(Presence.class));
}
/**
* Send a file to a user.
*
* @param file the file to send.
* @param jid the jid of the user to send the file to.
* @return the ChatRoom of the user.
*/
public ChatRoom sendFile(File file, String jid) {
long maxsize = Long.parseLong(Default.getString(Default.FILE_TRANSFER_MAXIMUM_SIZE));
long warningsize = Long.parseLong(Default.getString(Default.FILE_TRANSFER_WARNING_SIZE));
if(file.length()>= maxsize && maxsize != -1)
{
String maxsizeString = TransferUtils.getAppropriateByteWithSuffix(maxsize);
String yoursizeString = TransferUtils.getAppropriateByteWithSuffix(file.length());
String output = Res.getString("message.file.transfer.file.too.big.error", maxsizeString, yoursizeString);
JOptionPane.showMessageDialog(null, output, Res.getString("title.error"), JOptionPane.ERROR_MESSAGE);
return null;
}
if(file.length() >= warningsize && warningsize != -1)
{
int result = JOptionPane.showConfirmDialog(null, Res.getString("message.file.transfer.file.too.big.warning"), Res.getString("title.error"), JOptionPane.YES_NO_OPTION);
if(result != 0)
{
return null;
}
}
final ContactList contactList = SparkManager.getWorkspace().getContactList();
String bareJID = StringUtils.parseBareAddress(jid);
String fullJID = PresenceManager.getFullyQualifiedJID(jid);
if (!PresenceManager.isOnline(jid)) {
ArrayList<File> list = waitMap.get(jid);
if (list == null) {
list = new ArrayList<File>();
}
list.add(file);
waitMap.put(jid, list);
ChatRoom chatRoom;
ContactItem contactItem = contactList.getContactItemByJID(jid);
if (contactItem != null) {
chatRoom = SparkManager.getChatManager().createChatRoom(jid, contactItem.getDisplayName(), contactItem.getDisplayName());
}
else {
chatRoom = SparkManager.getChatManager().createChatRoom(jid, jid, jid);
}
chatRoom.getTranscriptWindow().insertNotificationMessage("The user is offline. Will auto-send \"" + file.getName() + "\" when user comes back online.", ChatManager.ERROR_COLOR);
return null;
}
// Create the outgoing file transfer
final OutgoingFileTransfer transfer = transferManager.createOutgoingFileTransfer(fullJID);
ContactItem contactItem = contactList.getContactItemByJID(bareJID);
ChatRoom chatRoom;
if (contactItem != null) {
chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getDisplayName(), contactItem.getDisplayName());
}
else {
chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID);
}
TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow();
SendFileTransfer sendingUI = new SendFileTransfer();
try {
transfer.sendFile(file, "Sending file");
}
catch (XMPPException e) {
Log.error(e);
}
// Add listener to cancel transfer is sending file to user who just went offline.
AndFilter presenceFilter = new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(bareJID));
final PacketListener packetListener = new PacketListener() {
public void processPacket(Packet packet) {
Presence presence = (Presence)packet;
if (!presence.isAvailable()) {
if (transfer != null) {
transfer.cancel();
}
}
}
};
// Add presence listener to check if user is offline and cancel sending.
SparkManager.getConnection().addPacketListener(packetListener, presenceFilter);
chatRoom.addClosingListener(new ChatRoomClosingListener() {
public void closing() {
SparkManager.getConnection().removePacketListener(packetListener);
if (!transfer.isDone()) {
transfer.cancel();
}
}
});
try {
sendingUI.sendFile(transfer, transferManager, fullJID, contactItem.getDisplayName());
}
catch (NullPointerException e) {
Log.error(e);
}
transcriptWindow.addComponent(sendingUI);
chatRoom.scrollToBottom();
return chatRoom;
}
/**
* Send an image to a user.
*
* @param image the image to send.
* @param room the ChatRoom of the user you wish to send the image to.
*/
public void sendImage(final BufferedImage image, final ChatRoom room) {
File tmpDirectory = new File(Spark.getSparkUserHome(), "/tempImages");
tmpDirectory.mkdirs();
String imageName = "image_" + StringUtils.randomString(2) + ".png";
final File imageFile = new File(tmpDirectory, imageName);
// Write image to system.
room.setCursor(new Cursor(Cursor.WAIT_CURSOR));
SwingWorker writeImageThread = new SwingWorker() {
public Object construct() {
try {
// Write out file in separate thread.
ImageIO.write(image, "png", imageFile);
}
catch (IOException e) {
Log.error(e);
}
return true;
}
public void finished() {
ChatRoomImpl roomImpl = (ChatRoomImpl)room;
sendFile(imageFile, roomImpl.getParticipantJID());
SparkManager.getChatManager().getChatContainer().activateChatRoom(room);
room.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
}
};
writeImageThread.start();
}
/**
* Returns an image if one is found in the clipboard, otherwise null is returned.
*
* @return the image in the clipboard if found, otherwise null.
*/
public static BufferedImage getClipboard() {
Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
try {
if (t != null && t.isDataFlavorSupported(DataFlavor.imageFlavor)) {
return (BufferedImage)t.getTransferData(DataFlavor.imageFlavor);
}
}
catch (UnsupportedFlavorException e) {
// Nothing to do
}
catch (IOException e) {
// Nothing to do
}
return null;
}
/**
* Adds a new TransferListener to the SparkManager. FileTransferListeners can be used
* to intercept incoming file transfers for own customizations. You may wish to not
* allow certain file transfers, or have your own UI to handle incoming files.
*
* @param listener the listener
*/
public void addTransferListener(FileTransferListener listener) {
listeners.add(listener);
}
/**
* Removes the FileTransferListener.
*
* @param listener the listener
*/
public void removeTransferListener(FileTransferListener listener) {
listeners.remove(listener);
}
private boolean fireTransferListeners(FileTransferRequest request) {
for (FileTransferListener listener : new ArrayList<FileTransferListener>(listeners)) {
boolean accepted = listener.handleTransfer(request);
if (accepted) {
return true;
}
}
return false;
}
/**
* Launches a file browser or opens a file with java Desktop.open() if is
* supported
*
* @param file
*/
private void launchFile(File file) {
if (!Desktop.isDesktopSupported())
return;
Desktop dt = Desktop.getDesktop();
try {
dt.open(file);
} catch (IOException ex) {
launchFile(file.getPath());
}
}
/**
* Launches a file browser or opens a file with java Desktop.open() if is
* supported
*
* @param filePath
*/
private void launchFile(String filePath) {
if (filePath == null || filePath.trim().length() == 0)
return;
if (!Desktop.isDesktopSupported())
return;
Desktop dt = Desktop.getDesktop();
try {
dt.browse(getFileURI(filePath));
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Sets the current default directory to store files.
*
* @param directory the default directory.
*/
public void setDefaultDirectory(File directory) {
defaultDirectory = directory;
}
/**
* Return the File Chooser to user.
* @param parent the parent component.
* @param title the title.
* @return the FileChooser. (Native Widget)
*/
public FileDialog getFileChooser(Frame parent, String title) {
return new FileDialog(parent, title, FileDialog.LOAD);
}
}