/** * $Revision: $ * $Date: $ * * Copyright (C) 2006 Jive Software. All rights reserved. * * This software is published under the terms of the GNU Lesser Public License (LGPL), * a copy of which is included in this distribution. */ package org.jivesoftware.spark.filetransfer; import org.jivesoftware.MainWindow; import org.jivesoftware.Spark; import org.jivesoftware.resource.Res; import org.jivesoftware.resource.SparkRes; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.Roster; 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.SparkManager; import org.jivesoftware.spark.component.RolloverButton; import org.jivesoftware.spark.filetransfer.preferences.FileTransferPreference; import org.jivesoftware.spark.preference.PreferenceManager; import org.jivesoftware.spark.ui.ChatFrame; import org.jivesoftware.spark.ui.ChatInputEditor; 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.ui.status.StatusBar; import org.jivesoftware.spark.util.GraphicUtils; import org.jivesoftware.spark.util.ResourceUtils; import org.jivesoftware.spark.util.SwingWorker; import org.jivesoftware.spark.util.WindowsFileSystemView; import org.jivesoftware.spark.util.log.Log; import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.Downloads; import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.ReceiveMessage; import org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui.SendMessage; import org.jivesoftware.sparkimpl.plugin.manager.Enterprise; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import java.awt.Component; import java.awt.Cursor; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; 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.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * 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 listeners = new ArrayList(); private File defaultDirectory; private static SparkTransferManager singleton; private static final Object LOCK = new Object(); private JFileChooser fc; private FileTransferManager transferManager; private Map waitMap = new HashMap(); /** * Returns the singleton instance of SparkTransferManager, * creating it if necessary. *

* * @return the singleton instance of SparkTransferManager */ 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; } // 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.add(downloadsMenu); downloadsMenu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Downloads downloads = Downloads.getInstance(); downloads.showDownloadsDirectory(); } }); // 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 files, Component component) { if (component instanceof ContactItem) { ContactItem item = (ContactItem)component; ChatRoom chatRoom = null; Iterator iter = files.iterator(); while (iter.hasNext()) { chatRoom = sendFile((File)iter.next(), item.getFullJID()); } 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 StatusBar statusBar = SparkManager.getWorkspace().getStatusBar(); JPanel commandPanel = statusBar.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) { Downloads downloads = Downloads.getInstance(); downloads.showDownloadsDirectory(); } }); } 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); ContactItem contactItem = contactList.getContactItemByJID(bareJID); ChatRoom chatRoom; if (contactItem != null) { chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getNickname(), contactItem.getNickname()); } else { chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID); } TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow(); StyledDocument doc = (StyledDocument)transcriptWindow.getDocument(); // The image must first be wrapped in a style Style style = doc.addStyle("StyleName", null); final ReceiveMessage receivingMessageUI = new ReceiveMessage(); receivingMessageUI.acceptFileTransfer(request); chatRoom.addClosingListener(new ChatRoomClosingListener() { public void closing() { receivingMessageUI.cancelTransfer(); } }); StyleConstants.setComponent(style, receivingMessageUI); // Insert the image at the end of the text try { doc.insertString(doc.getLength(), "ignored text", style); doc.insertString(doc.getLength(), "\n", null); } catch (BadLocationException e) { Log.error(e); } chatRoom.scrollToBottom(); SparkManager.getChatManager().getChatContainer().fireNotifyOnMessage(chatRoom); } public void sendFileTo(ContactItem item) { final ContactList contactList = SparkManager.getWorkspace().getContactList(); getFileChooser().setDialogTitle(Res.getString("title.select.file.to.send")); int ok = getFileChooser().showOpenDialog(contactList); if (ok != JFileChooser.APPROVE_OPTION) { return; } File[] files = getFileChooser().getSelectedFiles(); for (int i = 0; i < files.length; i++) { File file = files[i]; if (file.exists()) { defaultDirectory = file.getParentFile(); sendFile(file, item.getFullJID()); } } } private void addSendFileButton() { final ChatManager chatManager = SparkManager.getChatManager(); chatManager.addChatRoomListener(new ChatRoomListenerAdapter() { FileDropListener fileDropListener; public void chatRoomOpened(final ChatRoom room) { if (!(room instanceof ChatRoomImpl)) { return; } final ChatInputEditor chatSendField = room.getChatInputEditor(); chatSendField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("Ctrl v"), "paste"); chatSendField.getActionMap().put("paste", new AbstractAction("paste") { public void actionPerformed(ActionEvent evt) { String clipboardText = SparkManager.getClipboard(); if (clipboardText == null && getClipboard() != null) { sendImage(getClipboard(), room); } else { try { Document document = chatSendField.getDocument(); document.insertString(chatSendField.getCaretPosition(), clipboardText, null); } catch (BadLocationException e) { Log.error(e); } } } }); fileDropListener = new FileDropListener() { public void filesDropped(Collection files, Component component) { if (component instanceof ChatRoomImpl) { ChatRoomImpl roomImpl = (ChatRoomImpl)component; Iterator iter = files.iterator(); while (iter.hasNext()) { sendFile((File)iter.next(), roomImpl.getParticipantJID()); } SparkManager.getChatManager().getChatContainer().activateChatRoom(roomImpl); } } }; room.addFileDropListener(fileDropListener); ChatRoomButton sendFileButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.SEND_FILE_24x24)); sendFileButton.setToolTipText(Res.getString("message.send.file.to.user")); room.getToolBar().addChatRoomButton(sendFileButton); final ChatRoomButton sendScreenShotButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.PHOTO_IMAGE)); sendScreenShotButton.setToolTipText(Res.getString("message.send.picture")); room.getToolBar().addChatRoomButton(sendScreenShotButton); sendScreenShotButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { sendScreenshot(sendScreenShotButton, room); } }); sendFileButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { SwingWorker worker = new SwingWorker() { public Object construct() { try { Thread.sleep(10); } catch (InterruptedException e1) { Log.error(e1); } return true; } public void finished() { ChatRoomImpl roomImpl = (ChatRoomImpl)room; getFileChooser().setDialogTitle(Res.getString("title.select.file.to.send")); getFileChooser().setMultiSelectionEnabled(true); int ok = getFileChooser().showOpenDialog(roomImpl); if (ok != JFileChooser.APPROVE_OPTION) { return; } File[] file = getFileChooser().getSelectedFiles(); if (file == null || file.length == 0) { return; } final int no = file.length; for (int i = 0; i < no; i++) { File sendFile = file[i]; if (sendFile.exists()) { defaultDirectory = sendFile.getParentFile(); sendFile(sendFile, roomImpl.getParticipantJID()); } } } }; worker.start(); } }); } public void chatRoomClosed(ChatRoom room) { room.removeFileDropListener(fileDropListener); } }); } private 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); final Robot robot = new Robot(); Rectangle area = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()); return robot.createScreenCapture(area); } catch (Exception e) { Log.error(e); } return null; } public void finished() { final JFrame frame = new JFrame(); frame.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); final BufferedImage bufferedImage = (BufferedImage)get(); final ImageSelectionPanel mainPanel = new ImageSelectionPanel(bufferedImage); mainPanel.addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent e) { Rectangle clip = mainPanel.getClip(); BufferedImage newImage = null; try { newImage = bufferedImage.getSubimage((int)clip.getX(), (int)clip.getY(), (int)clip.getWidth(), (int)clip.getHeight()); } catch (Exception e1) { } if (newImage != null) { sendImage(newImage, room); } frame.dispose(); frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); if (mainWindowVisible) { mainWindow.setVisible(false); } if (chatFrameVisible) { chatFrame.setVisible(false); } } }); frame.addKeyListener(new KeyAdapter() { public void keyTyped(KeyEvent e) { if (e.getKeyChar() == KeyEvent.VK_ESCAPE) { frame.dispose(); frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); if (mainWindowVisible) { mainWindow.setVisible(false); } if (chatFrameVisible) { chatFrame.setVisible(false); } } } }); if (bufferedImage != null) { frame.setUndecorated(true); frame.setSize(bufferedImage.getWidth(null), bufferedImage.getHeight()); frame.getContentPane().add(mainPanel); // 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 } } button.setEnabled(true); } }; worker.start(); } private void addPresenceListener() { SparkManager.getConnection().addPacketListener(new PacketListener() { public void processPacket(Packet packet) { Presence presence = (Presence)packet; if (presence != null) { String bareJID = StringUtils.parseBareAddress(presence.getFrom()); // Iterate through map. List list = (List)waitMap.get(bareJID); if (list != null) { // Iterate through list and send. Iterator iter = list.iterator(); ChatRoom room = null; while (iter.hasNext()) { File 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 bJID the bare jid of the user to send the file to. * @return the ChatRoom of the user. */ public ChatRoom sendFile(File file, String bJID) { Roster roster = SparkManager.getConnection().getRoster(); Presence presence = roster.getPresence(bJID); final ContactList contactList = SparkManager.getWorkspace().getContactList(); if (presence == null) { List list = (List)waitMap.get(bJID); if (list == null) { list = new ArrayList(); } list.add(file); waitMap.put(bJID, list); ChatRoom chatRoom; ContactItem contactItem = contactList.getContactItemByJID(bJID); if (contactItem != null) { chatRoom = SparkManager.getChatManager().createChatRoom(bJID, contactItem.getNickname(), contactItem.getNickname()); } else { chatRoom = SparkManager.getChatManager().createChatRoom(bJID, bJID, bJID); } chatRoom.getTranscriptWindow().insertErrorMessage("The user is offline. Will auto-send \"" + file.getName() + "\" when user comes back online."); return null; } String bareJID = StringUtils.parseBareAddress(presence.getFrom()); // Create the outgoing file transfer final OutgoingFileTransfer transfer = transferManager.createOutgoingFileTransfer(presence.getFrom()); ContactItem contactItem = contactList.getContactItemByJID(bareJID); ChatRoom chatRoom; if (contactItem != null) { chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, contactItem.getNickname(), contactItem.getNickname()); } else { chatRoom = SparkManager.getChatManager().createChatRoom(bareJID, bareJID, bareJID); } TranscriptWindow transcriptWindow = chatRoom.getTranscriptWindow(); StyledDocument doc = (StyledDocument)transcriptWindow.getDocument(); // The image must first be wrapped in a style Style style = doc.addStyle("StyleName", null); SendMessage sendingUI = new SendMessage(); 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 == null || presence.getType() == Presence.Type.unavailable) { 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(); } } }); sendingUI.sendFile(transfer, transferManager, presence.getFrom(), contactItem.getNickname()); StyleConstants.setComponent(style, sendingUI); // Insert the image at the end of the text try { doc.insertString(doc.getLength(), "ignored text", style); doc.insertString(doc.getLength(), "\n", null); } catch (BadLocationException e) { Log.error(e); } 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 Image image, final ChatRoom room) { File tmpDirectory = new File(Spark.getUserHome(), "Spark/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 worker = new SwingWorker() { public Object construct() { try { // Write out file in separate thread. BufferedImage bi = GraphicUtils.convert(image); ImageIO.write(bi, "png", imageFile); } catch (InterruptedException e) { Log.error(e); } 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)); } }; worker.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 Image getClipboard() { Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null); try { if (t != null && t.isDataFlavorSupported(DataFlavor.imageFlavor)) { return (Image)t.getTransferData(DataFlavor.imageFlavor); } } catch (UnsupportedFlavorException e) { } catch (IOException e) { } 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) { final Iterator iter = new ArrayList(listeners).iterator(); while (iter.hasNext()) { FileTransferListener listener = (FileTransferListener)iter.next(); boolean accepted = listener.handleTransfer(request); if (accepted) { return true; } } return false; } private JFileChooser getFileChooser() { if (fc == null) { fc = new JFileChooser(defaultDirectory); fc.setMultiSelectionEnabled(true); if (Spark.isWindows()) { fc.setFileSystemView(new WindowsFileSystemView()); } } return fc; } }