From c98f98655f7dd2a7eb21868a08032554fe0c97b9 Mon Sep 17 00:00:00 2001 From: Yana Stamcheva Date: Tue, 28 Jul 2009 16:07:46 +0000 Subject: [PATCH] - Some improvements in "feature" introducing in Jabber. - Support for thumbnails in Jabber file transfer. - Fixed some issues with the calculated file transfer speed and estimated transfer time. - Shows open and open folder links also on the sender side when the transfer is completed. --- lib/installer-exclude/smack.manifest.mf | 1 + .../filehistory/FileHistoryServiceImpl.java | 4 +- .../impl/gui/main/chat/ChatPanel.java | 1 + .../main/chat/MetaContactChatTransport.java | 66 ++++ .../FileTransferConversationComponent.java | 77 ++--- .../ReceiveFileConversationComponent.java | 37 ++- .../SendFileConversationComponent.java | 19 +- .../impl/protocol/icq/FileTransferImpl.java | 20 +- .../IncomingFileTransferRequestIcqImpl.java | 11 +- .../IncomingFileTransferJabberImpl.java | 31 +- ...IncomingFileTransferRequestJabberImpl.java | 107 ++++++- .../OperationSetFileTransferJabberImpl.java | 181 ++++++++--- ...perationSetThumbnailedFileFactoryImpl.java | 44 +++ .../OutgoingFileTransferJabberImpl.java | 185 ++++++++++- .../ProtocolProviderServiceJabberImpl.java | 118 ++++++- .../impl/protocol/jabber/ThumbnailedFile.java | 86 +++++ .../GeolocationPacketExtension.java | 2 +- .../extensions/thumbnail/FileElement.java | 269 ++++++++++++++++ .../thumbnail/ThumbnailElement.java | 298 ++++++++++++++++++ .../extensions/thumbnail/ThumbnailIQ.java | 171 ++++++++++ .../jabber/jabber.provider.manifest.mf | 1 + .../protocol/mock/MockFileTransferImpl.java | 6 +- .../mock/MockOperationSetFileTransfer.java | 4 + .../protocol/ssh/FileTransferSSHImpl.java | 2 +- ...rationSetBasicInstantMessagingSSHImpl.java | 39 ++- .../ssh/OperationSetFileTransferSSHImpl.java | 2 + .../impl/protocol/yahoo/FileTransferImpl.java | 2 +- .../IncomingFileTransferRequestYahooImpl.java | 10 + .../protocol/AbstractFileTransfer.java | 15 +- .../service/protocol/FileTransfer.java | 4 +- .../protocol/IncomingFileTransferRequest.java | 9 +- .../protocol/OperationSetFileTransfer.java | 10 +- .../OperationSetThumbnailedFileFactory.java | 37 +++ .../event/FileTransferStatusChangeEvent.java | 19 +- .../sip/communicator/util/ImageUtils.java | 35 ++ .../sip/communicator/util/Sha1Crypto.java | 76 +++++ .../generic/TestOperationSetFileTransfer.java | 25 +- 37 files changed, 1821 insertions(+), 203 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/OperationSetThumbnailedFileFactoryImpl.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/ThumbnailedFile.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/FileElement.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailElement.java create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailIQ.java create mode 100644 src/net/java/sip/communicator/service/protocol/OperationSetThumbnailedFileFactory.java create mode 100644 src/net/java/sip/communicator/util/Sha1Crypto.java diff --git a/lib/installer-exclude/smack.manifest.mf b/lib/installer-exclude/smack.manifest.mf index 499bb807a..44a8cfc9f 100644 --- a/lib/installer-exclude/smack.manifest.mf +++ b/lib/installer-exclude/smack.manifest.mf @@ -30,5 +30,6 @@ Export-Package: org.jivesoftware.smack, org.jivesoftware.smackx.jingle.provider, org.jivesoftware.smackx.jingle.listeners, org.jivesoftware.smackx.filetransfer, + org.jivesoftware.smackx.provider, org.xmlpull.v1, org.xmlpull.mxp1 diff --git a/src/net/java/sip/communicator/impl/filehistory/FileHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/filehistory/FileHistoryServiceImpl.java index 7e2c9937b..aacc98a60 100644 --- a/src/net/java/sip/communicator/impl/filehistory/FileHistoryServiceImpl.java +++ b/src/net/java/sip/communicator/impl/filehistory/FileHistoryServiceImpl.java @@ -785,12 +785,12 @@ public void fileTransferCreated(FileTransferCreatedEvent event) STRUCTURE_NAMES[4], fileTransfer.getID(), STRUCTURE_NAMES[0], - fileTransfer.getFile().getCanonicalPath()); + fileTransfer.getLocalFile().getCanonicalPath()); } else if (fileTransfer.getDirection() == FileTransfer.OUT) { historyWriter.addRecord(new String[]{ - fileTransfer.getFile().getCanonicalPath(), + fileTransfer.getLocalFile().getCanonicalPath(), getDirection(FileTransfer.OUT), String.valueOf(event.getTimestamp().getTime()), FILE_TRANSFER_ACTIVE, diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java index f40ec4413..78c5981c8 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatPanel.java @@ -1906,6 +1906,7 @@ public void statusChanged(FileTransferStatusChangeEvent event) || newStatus == FileTransferStatusChangeEvent.REFUSED) { removeActiveFileTransfer(fileTransfer.getID()); + fileTransfer.removeStatusListener(this); } } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/MetaContactChatTransport.java b/src/net/java/sip/communicator/impl/gui/main/chat/MetaContactChatTransport.java index 48c9930ed..5c6bf88c8 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/MetaContactChatTransport.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/MetaContactChatTransport.java @@ -8,6 +8,9 @@ package net.java.sip.communicator.impl.gui.main.chat; import java.io.*; +import java.net.*; + +import javax.swing.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; @@ -32,6 +35,10 @@ public class MetaContactChatTransport private final OperationSetPresence presenceOpSet; + private static final int THUMBNAIL_WIDTH = 64; + + private static final int THUMBNAIL_HEIGHT = 64; + public MetaContactChatTransport(ChatSession chatSession, Contact contact) { @@ -281,6 +288,27 @@ public FileTransfer sendFile(File file) = (OperationSetFileTransfer) contact.getProtocolProvider() .getOperationSet(OperationSetFileTransfer.class); + if (FileUtils.isImage(file.getName())) + { + // Create a thumbnailed file if possible. + OperationSetThumbnailedFileFactory tfOpSet + = (OperationSetThumbnailedFileFactory) + contact.getProtocolProvider() + .getOperationSet(OperationSetThumbnailedFileFactory.class); + + if (tfOpSet != null) + { + byte[] thumbnail = getFileThumbnail(file); + + if (thumbnail != null && thumbnail.length > 0) + { + file = tfOpSet.createFileWithThumbnail( + file, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, + "image/png", thumbnail); + } + } + } + return ftOpSet.sendFile(contact, file); } @@ -426,4 +454,42 @@ public Object getDescriptor() { return contact; } + + /** + * Sets the icon for the given file. + * + * @param file the file to set an icon for + */ + private byte[] getFileThumbnail(File file) + { + byte[] bytes = null; + if (FileUtils.isImage(file.getName())) + { + try + { + ImageIcon image = new ImageIcon(file.toURI().toURL()); + + if (image != null) + { + int width = image.getIconWidth(); + int height = image.getIconHeight(); + + if (width > THUMBNAIL_WIDTH) + width = THUMBNAIL_WIDTH; + if (height > THUMBNAIL_HEIGHT) + height = THUMBNAIL_HEIGHT; + + bytes = ImageUtils + .getScaledInstanceInBytes(image.getImage(), + width, height); + } + } + catch (MalformedURLException e) + { + logger.debug("Could not locate image.", e); + } + } + + return bytes; + } } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/FileTransferConversationComponent.java b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/FileTransferConversationComponent.java index f691ba883..df4898205 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/FileTransferConversationComponent.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/FileTransferConversationComponent.java @@ -9,7 +9,6 @@ import java.awt.*; import java.awt.event.*; import java.io.*; -import java.net.*; import javax.swing.*; @@ -35,7 +34,11 @@ public abstract class FileTransferConversationComponent private final Logger logger = Logger.getLogger(FileTransferConversationComponent.class); - private final FileImageLabel imageLabel = new FileImageLabel(); + protected static final int IMAGE_WIDTH = 64; + + protected static final int IMAGE_HEIGHT = 64; + + protected final FileImageLabel imageLabel = new FileImageLabel(); protected final JLabel titleLabel = new JLabel(); protected final JLabel fileLabel = new JLabel(); private final JTextArea errorArea = new JTextArea(); @@ -70,6 +73,10 @@ public abstract class FileTransferConversationComponent private FileTransfer fileTransfer; + private final static int SPEED_CALCULATE_DELAY = 5000; + + private long transferredFileSize = 0; + private long lastSpeedTimestamp = 0; private long lastEstimatedTimeTimestamp = 0; @@ -293,43 +300,6 @@ protected void showErrorMessage(String message) errorArea.setVisible(true); } - /** - * Sets the icon for the given file. - * - * @param file the file to set an icon for - */ - protected void setFileIcon(File file) - { - if (FileUtils.isImage(file.getName())) - { - try - { - ImageIcon image = new ImageIcon(file.toURI().toURL()); - imageLabel.setToolTipImage(image); - - image = ImageUtils - .getScaledRoundedIcon(image.getImage(), 64, 64); - imageLabel.setIcon(image); - } - catch (MalformedURLException e) - { - logger.debug("Could not locate image.", e); - imageLabel.setIcon(new ImageIcon( - ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON))); - } - } - else - { - Icon icon = FileUtils.getIcon(file); - - if (icon == null) - icon = new ImageIcon( - ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON)); - - imageLabel.setIcon(icon); - } - } - /** * Sets the download file. * @@ -361,9 +331,11 @@ public void mouseClicked(MouseEvent e) * * @param fileTransfer the file transfer */ - protected void setFileTransfer(FileTransfer fileTransfer) + protected void setFileTransfer( FileTransfer fileTransfer, + long transferredFileSize) { this.fileTransfer = fileTransfer; + this.transferredFileSize = transferredFileSize; fileTransfer.addProgressListener(this); } @@ -450,7 +422,7 @@ else if (sourceButton.equals(cancelButton)) */ public void progressChanged(FileTransferProgressEvent event) { - progressBar.setValue((int) event.getProgress()); + progressBar.setValue((int)event.getProgress()); long transferredBytes = event.getFileTransfer().getTransferedBytes(); long progressTimestamp = event.getTimestamp(); @@ -458,7 +430,8 @@ public void progressChanged(FileTransferProgressEvent event) ByteFormat format = new ByteFormat(); String bytesString = format.format(transferredBytes); - if ((progressTimestamp - lastSpeedTimestamp) >= 5000) + if ((progressTimestamp - lastSpeedTimestamp) + >= SPEED_CALCULATE_DELAY) { lastProgressSpeed = Math.round(calculateProgressSpeed(transferredBytes)); @@ -467,12 +440,13 @@ public void progressChanged(FileTransferProgressEvent event) this.lastTransferredBytes = transferredBytes; } - if ((progressTimestamp - lastEstimatedTimeTimestamp) >= 5000 + if ((progressTimestamp - lastEstimatedTimeTimestamp) + >= SPEED_CALCULATE_DELAY && lastProgressSpeed > 0) { lastEstimatedTime = Math.round(calculateEstimatedTransferTime( lastProgressSpeed, - event.getFileTransfer().getFile().length() - transferredBytes)); + transferredFileSize - transferredBytes)); lastEstimatedTimeTimestamp = progressTimestamp; } @@ -483,7 +457,7 @@ public void progressChanged(FileTransferProgressEvent event) { progressSpeedLabel.setText( resources.getI18NString("service.gui.SPEED") - + format.format(lastProgressSpeed)); + + format.format(lastProgressSpeed) + "/sec"); progressSpeedLabel.setVisible(true); } @@ -499,6 +473,7 @@ public void progressChanged(FileTransferProgressEvent event) /** * Returns the string, showing information for the given file. * + * * @param file the file * @return the name of the given file */ @@ -556,15 +531,17 @@ protected void hideProgressRelatedComponents() */ private double calculateProgressSpeed(long transferredBytes) { - // Bytes per second = bytes per 5000 miliseconds * 1000. - return (transferredBytes - lastTransferredBytes) / 5; + // Bytes per second = bytes / SPEED_CALCULATE_DELAY miliseconds * 1000. + return (transferredBytes - lastTransferredBytes) + / SPEED_CALCULATE_DELAY * 1000; } /** + * Returns the estimated transfer time left. * - * @param speed - * @param fileSize - * @return + * @param speed the speed of the transfer + * @param fileSize the size of the file + * @return the estimated transfer time left */ private double calculateEstimatedTransferTime(double speed, long bytesLeft) { diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java index aa85a3ba8..4ed81dc93 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/ReceiveFileConversationComponent.java @@ -10,6 +10,8 @@ import java.io.*; import java.util.*; +import javax.swing.*; + import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.*; import net.java.sip.communicator.service.protocol.*; @@ -43,6 +45,8 @@ public class ReceiveFileConversationComponent private final String dateString; + private File downloadFile; + /** * Creates a ReceiveFileConversationComponent. * @@ -63,6 +67,23 @@ public ReceiveFileConversationComponent( fileTransferOpSet.addFileTransferListener(this); + byte[] thumbnail = request.getThumbnail(); + + if (thumbnail != null && thumbnail.length > 0) + { + ImageIcon thumbnailIcon = new ImageIcon(thumbnail); + + if (thumbnailIcon.getIconWidth() > IMAGE_WIDTH + || thumbnailIcon.getIconHeight() > IMAGE_HEIGHT) + { + thumbnailIcon + = ImageUtils.getScaledRoundedIcon( + thumbnail, IMAGE_WIDTH, IMAGE_WIDTH); + } + + imageLabel.setIcon(thumbnailIcon); + } + titleLabel.setText( dateString + resources.getI18NString( @@ -89,7 +110,7 @@ public void actionPerformed(ActionEvent e) cancelButton.setVisible(true); progressBar.setVisible(true); - File downloadFile = createFile(fileTransferRequest); + downloadFile = createFile(fileTransferRequest); new AcceptFile(downloadFile).start(); } @@ -175,12 +196,20 @@ private File createFile(IncomingFileTransferRequest fileTransferRequest) */ public void statusChanged(FileTransferStatusChangeEvent event) { - int status = event.getNewStatus(); FileTransfer fileTransfer = event.getFileTransfer(); + int status = event.getNewStatus(); String fromContactName = fileTransferRequest.getSender().getDisplayName(); + if (status == FileTransferStatusChangeEvent.COMPLETED + || status == FileTransferStatusChangeEvent.CANCELED + || status == FileTransferStatusChangeEvent.FAILED + || status == FileTransferStatusChangeEvent.REFUSED) + { + fileTransfer.removeStatusListener(this); + } + if (status == FileTransferStatusChangeEvent.PREPARING) { hideProgressRelatedComponents(); @@ -219,7 +248,7 @@ else if (status == FileTransferStatusChangeEvent.IN_PROGRESS) } else if (status == FileTransferStatusChangeEvent.COMPLETED) { - this.setCompletedDownloadFile(fileTransfer.getFile()); + this.setCompletedDownloadFile(downloadFile); hideProgressRelatedComponents(); cancelButton.setVisible(false); @@ -312,7 +341,7 @@ public void finished() { if (fileTransfer != null) { - setFileTransfer(fileTransfer); + setFileTransfer(fileTransfer, fileTransferRequest.getFileSize()); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/SendFileConversationComponent.java b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/SendFileConversationComponent.java index 55614a175..7e1f4a7f0 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/SendFileConversationComponent.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/filetransfer/SendFileConversationComponent.java @@ -34,6 +34,8 @@ public class SendFileConversationComponent private final String dateString; + private final File file; + /** * Creates a SendFileConversationComponent by specifying the parent * chat panel, where this component is added, the destination contact of @@ -49,6 +51,7 @@ public SendFileConversationComponent( ChatPanel chatPanel, { this.parentChatPanel = chatPanel; this.toContactName = toContactName; + this.file = file; // Create the date that would be shown in the component. this.date = new Date(); @@ -93,7 +96,7 @@ public void actionPerformed(ActionEvent e) */ public void setProtocolFileTransfer(FileTransfer fileTransfer) { - this.setFileTransfer(fileTransfer); + this.setFileTransfer(fileTransfer, file.length()); fileTransfer.addStatusListener(this); } @@ -104,8 +107,18 @@ public void setProtocolFileTransfer(FileTransfer fileTransfer) */ public void statusChanged(FileTransferStatusChangeEvent event) { + FileTransfer fileTransfer = event.getFileTransfer(); int status = event.getNewStatus(); + if (status == FileTransferStatusChangeEvent.COMPLETED + || status == FileTransferStatusChangeEvent.CANCELED + || status == FileTransferStatusChangeEvent.FAILED + || status == FileTransferStatusChangeEvent.REFUSED) + { + parentChatPanel.removeActiveFileTransfer(fileTransfer.getID()); + fileTransfer.removeStatusListener(this); + } + if (status == FileTransferStatusChangeEvent.PREPARING) { hideProgressRelatedComponents(); @@ -153,8 +166,12 @@ else if (status == FileTransferStatusChangeEvent.COMPLETED) + resources.getI18NString( "service.gui.FILE_SEND_COMPLETED", new String[]{toContactName})); + cancelButton.setVisible(false); retryButton.setVisible(false); + + openFileButton.setVisible(true); + openFolderButton.setVisible(true); } else if (status == FileTransferStatusChangeEvent.CANCELED) { diff --git a/src/net/java/sip/communicator/impl/protocol/icq/FileTransferImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/FileTransferImpl.java index a0a242772..11095db45 100644 --- a/src/net/java/sip/communicator/impl/protocol/icq/FileTransferImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/icq/FileTransferImpl.java @@ -18,7 +18,7 @@ import net.kano.joustsim.oscar.oscar.service.icbm.ft.state.*; /** - * The Filetransfer imeplementation for icq. + * The Filetransfer imeplementation for ICQ. * @author Damian Minkov */ public class FileTransferImpl @@ -90,22 +90,22 @@ public int getDirection() } /** - * Returns the file that is transfered. - * - * @return the file + * Returns the contact that we are transferring files with. + * @return the contact. */ - public File getFile() + public Contact getContact() { - return file; + return contact; } /** - * Returns the contact that we are transfering files with. - * @return the contact. + * Returns the local file that is being transferred or to which we transfer. + * + * @return the file */ - public Contact getContact() + public File getLocalFile() { - return contact; + return file; } /** diff --git a/src/net/java/sip/communicator/impl/protocol/icq/IncomingFileTransferRequestIcqImpl.java b/src/net/java/sip/communicator/impl/protocol/icq/IncomingFileTransferRequestIcqImpl.java index bec359e0c..a500ffab4 100644 --- a/src/net/java/sip/communicator/impl/protocol/icq/IncomingFileTransferRequestIcqImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/icq/IncomingFileTransferRequestIcqImpl.java @@ -212,5 +212,14 @@ public File getUnspecifiedFilename() return file; } } - + + /** + * Returns the thumbnail contained in this request. + * + * @return the thumbnail contained in this request + */ + public byte[] getThumbnail() + { + return null; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferJabberImpl.java index e707c7bdc..ba2440a63 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferJabberImpl.java @@ -20,10 +20,11 @@ public class IncomingFileTransferJabberImpl extends AbstractFileTransfer { - private String id = null; + private final String id; - private Contact sender = null; - private File file = null; + private final Contact sender; + + private final File file; /** * The Jabber incoming file transfer. @@ -33,9 +34,9 @@ public class IncomingFileTransferJabberImpl /** * Creates an IncomingFileTransferJabberImpl. * + * @param id the identifier of this transfer * @param sender the sender of the file * @param file the file - * @param date the date on which the request was received * @param jabberTransfer the Jabber file transfer object */ public IncomingFileTransferJabberImpl( String id, @@ -43,10 +44,10 @@ public IncomingFileTransferJabberImpl( String id, File file, IncomingFileTransfer jabberTransfer) { - this.jabberTransfer = jabberTransfer; this.id = id; this.sender = sender; this.file = file; + this.jabberTransfer = jabberTransfer; } /** @@ -77,16 +78,6 @@ public int getDirection() return IN; } - /** - * The file we are receiving. - * - * @return file we are receiving - */ - public File getFile() - { - return file; - } - /** * Returns the sender of the file. * @@ -106,4 +97,14 @@ public String getID() { return id; } + + /** + * Returns the local file that is being transferred or to which we transfer. + * + * @return the file + */ + public File getLocalFile() + { + return file; + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferRequestJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferRequestJabberImpl.java index b76131721..e17a9d692 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferRequestJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IncomingFileTransferRequestJabberImpl.java @@ -6,23 +6,26 @@ */ package net.java.sip.communicator.impl.protocol.jabber; -import java.io.File; +import java.io.*; import java.util.*; -import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.util.*; -import org.jivesoftware.smackx.filetransfer.*; - +import net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.FileTransfer; import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.Logger; +import net.java.sip.communicator.util.*; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.filetransfer.*; /** * Jabber implementation of the incoming file transfer request * * @author Nicolas Riegel - * + * @author Yana Stamcheva */ public class IncomingFileTransferRequestJabberImpl implements IncomingFileTransferRequest @@ -42,9 +45,13 @@ public class IncomingFileTransferRequestJabberImpl private final OperationSetFileTransferJabberImpl fileTransferOpSet; + private final ProtocolProviderServiceJabberImpl jabberProvider; + private Contact sender; - private Date date; + private String thumbnailCid; + + private byte[] thumbnail; /** * Creates an IncomingFileTransferRequestJabberImpl based on the @@ -53,17 +60,17 @@ public class IncomingFileTransferRequestJabberImpl * @param jabberProvider the protocol provider * @param fileTransferOpSet file transfer operation set * @param fileTransferRequest the request coming from the Jabber protocol - * @param date the date on which this request was received + * @param thumbnailCid the content-ID used to match the thumbnail that + * would be send after this request is created. */ public IncomingFileTransferRequestJabberImpl( ProtocolProviderServiceJabberImpl jabberProvider, OperationSetFileTransferJabberImpl fileTransferOpSet, - FileTransferRequest fileTransferRequest, - Date date) + FileTransferRequest fileTransferRequest) { + this.jabberProvider = jabberProvider; this.fileTransferOpSet = fileTransferOpSet; this.fileTransferRequest = fileTransferRequest; - this.date = date; String fromUserID = StringUtils.parseBareAddress(fileTransferRequest.getRequestor()); @@ -163,7 +170,7 @@ public void rejectFile() fileTransferRequest.reject(); fileTransferOpSet.fireFileTransferRequestRejected( - new FileTransferRequestEvent(fileTransferOpSet, this, this.date)); + new FileTransferRequestEvent(fileTransferOpSet, this, new Date())); } /** @@ -174,4 +181,78 @@ public String getID() { return id; } + + /** + * Returns the thumbnail contained in this request. + * + * @return the thumbnail contained in this request + */ + public byte[] getThumbnail() + { + return thumbnail; + } + + /** + * Sets the thumbnail content-ID. + * @param cid the thumbnail content-ID + */ + public void createThumbnailListeners(String cid) + { + this.thumbnailCid = cid; + + if (jabberProvider.getConnection() != null) + { + jabberProvider.getConnection().addPacketListener( + new ThumbnailResponseListener(), + new AndFilter( new PacketTypeFilter(IQ.class), + new IQTypeFilter(IQ.Type.RESULT))); + } + } + + /** + * The ThumbnailResponseListener listens for events triggered by + * the reception of a ThumbnailIQ packet. The packet is examined + * and a file transfer request event is fired when the thumbnail is + * extracted. + */ + private class ThumbnailResponseListener implements PacketListener + { + public void processPacket(Packet packet) + { + // If this is not an IQ packet, we're not interested. + if (!(packet instanceof ThumbnailIQ)) + return; + + logger.debug("Thumbnail response received."); + + ThumbnailIQ thumbnailResponse = (ThumbnailIQ) packet; + + if (thumbnailResponse.getCid() != null + && thumbnailResponse.getCid().equals(thumbnailCid)) + { + thumbnail = thumbnailResponse.getData(); + + // Create an event associated to this global request. + FileTransferRequestEvent fileTransferRequestEvent + = new FileTransferRequestEvent( + fileTransferOpSet, + IncomingFileTransferRequestJabberImpl.this, + new Date()); + + // Notify the global listener that a request has arrived. + fileTransferOpSet.fireFileTransferRequest( + fileTransferRequestEvent); + } + else + { + //TODO: RETURN + } + + if (jabberProvider.getConnection() != null) + { + jabberProvider.getConnection() + .removePacketListener(this); + } + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetFileTransferJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetFileTransferJabberImpl.java index 72e8ccf1e..23ad64aeb 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetFileTransferJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetFileTransferJabberImpl.java @@ -10,6 +10,7 @@ import java.io.*; import java.util.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.FileTransfer; import net.java.sip.communicator.service.protocol.event.*; @@ -17,9 +18,12 @@ import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; import org.jivesoftware.smackx.filetransfer.*; import org.jivesoftware.smackx.filetransfer.FileTransfer.*; +import org.jivesoftware.smackx.packet.*; /** * The Jabber implementation of the OperationSetFileTransfer @@ -51,7 +55,7 @@ public class OperationSetFileTransferJabberImpl /** * The Jabber file transfer listener. */ - private JabberFileTransferListener jabberFileTransferListener; + private FileTransferRequestListener fileTransferRequestListener; /** * A list of listeners registered for file transfer events. @@ -87,23 +91,33 @@ public OperationSetFileTransferJabberImpl( public FileTransfer sendFile( Contact toContact, File file) throws IllegalStateException, - IllegalArgumentException + IllegalArgumentException, + OperationNotSupportedException { - AbstractFileTransfer outgoingTransfer = null; + OutgoingFileTransferJabberImpl outgoingTransfer = null; try { assertConnected(); - Roster roster = jabberProvider.getConnection().getRoster(); - Presence presence = roster.getPresence(toContact.getAddress()); + String fullJid = jabberProvider.getFullJid(toContact); + + // First we check if file transfer is at all supported for this + // contact. + if (!jabberProvider.isFeatureListSupported(fullJid, + new String[]{"http://jabber.org/protocol/si", + "http://jabber.org/protocol/si/profile/file-transfer"})) + { + new OperationNotSupportedException( + "Contact client or server does not support file transfers."); + } OutgoingFileTransfer transfer - = manager.createOutgoingFileTransfer(presence.getFrom()); + = manager.createOutgoingFileTransfer(fullJid); outgoingTransfer = new OutgoingFileTransferJabberImpl( - toContact, file, transfer); + toContact, file, transfer, jabberProvider); // Notify all interested listeners that a file transfer has been // created. @@ -113,7 +127,7 @@ public FileTransfer sendFile( Contact toContact, fireFileTransferCreated(event); // Send the file through the Jabber file transfer. - transfer.sendFile(file, "Sending file."); + transfer.sendFile(file, "Sending file"); // Start the status and progress thread. new FileTransferProgressThread( @@ -144,7 +158,8 @@ public FileTransfer sendFile( Contact toContact, String remotePath, String localPath) throws IllegalStateException, - IllegalArgumentException + IllegalArgumentException, + OperationNotSupportedException { return this.sendFile(toContact, new File(localPath)); } @@ -226,23 +241,46 @@ public void registrationStateChanged(RegistrationStateChangeEvent evt) manager = new FileTransferManager( jabberProvider.getConnection()); - // Create the Jabber file transfer listener. - jabberFileTransferListener = new JabberFileTransferListener(); + fileTransferRequestListener = new FileTransferRequestListener(); + + ProviderManager.getInstance().addIQProvider( + FileElement.ELEMENT_NAME, + FileElement.NAMESPACE, + new FileElement()); + + ProviderManager.getInstance().addIQProvider( + ThumbnailIQ.ELEMENT_NAME, + ThumbnailIQ.NAMESPACE, + new ThumbnailIQ()); - // Add the Jabber file transfer listener to the manager. - manager.addFileTransferListener(jabberFileTransferListener); + jabberProvider.getConnection().addPacketListener( + fileTransferRequestListener, + new AndFilter( new PacketTypeFilter(StreamInitiation.class), + new IQTypeFilter(IQ.Type.SET))); } else if (evt.getNewState() == RegistrationState.UNREGISTERED) { - if(jabberFileTransferListener != null - && manager != null) + if(fileTransferRequestListener != null + && jabberProvider.getConnection() != null) { - manager.removeFileTransferListener( - jabberFileTransferListener); + jabberProvider.getConnection().removePacketListener( + fileTransferRequestListener); + } + + ProviderManager providerManager = ProviderManager.getInstance(); + if (providerManager != null) + { + ProviderManager.getInstance().removeIQProvider( + FileElement.ELEMENT_NAME, + FileElement.NAMESPACE); - manager = null; - jabberFileTransferListener = null; + ProviderManager.getInstance().removeIQProvider( + ThumbnailIQ.ELEMENT_NAME, + ThumbnailIQ.NAMESPACE); } + + fileTransferRequestListener = null; + manager = null; } } } @@ -250,36 +288,69 @@ else if (evt.getNewState() == RegistrationState.UNREGISTERED) /** * Listener for Jabber incoming file transfer requests. */ - private class JabberFileTransferListener - implements org.jivesoftware.smackx.filetransfer.FileTransferListener + private class FileTransferRequestListener implements PacketListener { - /** - * Function called when a jabber file transfer request arrive. - */ - public void fileTransferRequest(FileTransferRequest request) + public void processPacket(Packet packet) { - logger.debug("Incoming Jabber file transfer request."); + if (!(packet instanceof StreamInitiation)) + return; + + logger.debug("Incoming Jabber file transfer request."); - // Create the date on which the request was received. - Date requestDate = new Date(); + StreamInitiation streamInitiation = (StreamInitiation) packet; + + FileTransferRequest jabberRequest + = new FileTransferRequest(manager, streamInitiation); // Create a global incoming file transfer request. - IncomingFileTransferRequest incomingFileTransferRequest + IncomingFileTransferRequestJabberImpl incomingFileTransferRequest = new IncomingFileTransferRequestJabberImpl( jabberProvider, OperationSetFileTransferJabberImpl.this, - request, - requestDate); - - // Create an event associated to this global request. - FileTransferRequestEvent fileTransferRequestEvent - = new FileTransferRequestEvent( - OperationSetFileTransferJabberImpl.this, - incomingFileTransferRequest, - requestDate); - - // Notify the global listener that a request has arrived. - fireFileTransferRequest(fileTransferRequestEvent); + jabberRequest); + + // Send a thumbnail request if a thumbnail is advertised in the + // streamInitiation packet. + org.jivesoftware.smackx.packet.StreamInitiation.File file + = streamInitiation.getFile(); + + boolean isThumbnailedFile = false; + if (file instanceof FileElement) + { + ThumbnailElement thumbnailElement + = ((FileElement) file).getThumbnailElement(); + + if (thumbnailElement != null) + { + isThumbnailedFile = true; + incomingFileTransferRequest + .createThumbnailListeners(thumbnailElement.getCid()); + + ThumbnailIQ thumbnailRequest + = new ThumbnailIQ( streamInitiation.getTo(), + streamInitiation.getFrom(), + thumbnailElement.getCid(), + IQ.Type.GET); + + logger.debug("Sending thumbnail request:" + + thumbnailRequest.toXML()); + + jabberProvider.getConnection().sendPacket(thumbnailRequest); + } + } + + if (!isThumbnailedFile) + { + // Create an event associated to this global request. + FileTransferRequestEvent fileTransferRequestEvent + = new FileTransferRequestEvent( + OperationSetFileTransferJabberImpl.this, + incomingFileTransferRequest, + new Date()); + + // Notify the global listener that a request has arrived. + fireFileTransferRequest(fileTransferRequestEvent); + } } } @@ -289,7 +360,7 @@ public void fileTransferRequest(FileTransferRequest request) * @param event the EventObject that we'd like delivered to all * registered file transfer listeners. */ - private void fireFileTransferRequest(FileTransferRequestEvent event) + void fireFileTransferRequest(FileTransferRequestEvent event) { Iterator listeners = null; synchronized (fileTransferListeners) @@ -386,6 +457,7 @@ public void run() { int status; long progress; + String statusReason = ""; while (true) { @@ -401,10 +473,17 @@ public void run() || status == FileTransferStatusChangeEvent.CANCELED || status == FileTransferStatusChangeEvent.REFUSED) { + if (fileTransfer instanceof + OutgoingFileTransferJabberImpl) + { + ((OutgoingFileTransferJabberImpl) fileTransfer) + .removeThumbnailRequestListener(); + } + break; } - fileTransfer.fireStatusChangeEvent(status); + fileTransfer.fireStatusChangeEvent(status, "Status changed"); fileTransfer.fireProgressChangeEvent( System.currentTimeMillis(), progress); } @@ -414,6 +493,20 @@ public void run() } } + if (jabberTransfer.getError() != null) + { + logger.error("An error occured while transfering file: " + + jabberTransfer.getError().getMessage()); + } + + if (jabberTransfer.getException() != null) + { + logger.error("An exception occured while transfering file: ", + jabberTransfer.getException()); + + statusReason = jabberTransfer.getException().getMessage(); + } + if (initialFileSize > 0 && status == FileTransferStatusChangeEvent.COMPLETED && fileTransfer.getTransferedBytes() < initialFileSize) @@ -421,7 +514,7 @@ public void run() status = FileTransferStatusChangeEvent.CANCELED; } - fileTransfer.fireStatusChangeEvent(status); + fileTransfer.fireStatusChangeEvent(status, statusReason); fileTransfer.fireProgressChangeEvent( System.currentTimeMillis(), progress); } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetThumbnailedFileFactoryImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetThumbnailedFileFactoryImpl.java new file mode 100644 index 000000000..8c2962f75 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetThumbnailedFileFactoryImpl.java @@ -0,0 +1,44 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.io.*; + +import net.java.sip.communicator.service.protocol.*; + +/** + * The OperationSetThumbnailedFileFactory is meant to be used by + * bundles interested in making files with thumbnails. For example the user + * interface can be interested in sending files with thumbnails through the + * OperationSetFileTransfer. + * + * @author Yana Stamcheva + */ +public class OperationSetThumbnailedFileFactoryImpl + implements OperationSetThumbnailedFileFactory +{ + /** + * Creates a file, by attaching the thumbnail, given by the details, to it. + * + * @param file the base file + * @param thumbnailWidth the width of the thumbnail + * @param thumbnailHeight the height of the thumbnail + * @param thumbnailMimeType the mime type of the thumbnail + * @param thumbnail the thumbnail data + * @return a file with a thumbnail + */ + public File createFileWithThumbnail(File file, + int thumbnailWidth, + int thumbnailHeight, + String thumbnailMimeType, + byte[] thumbnail) + { + return new ThumbnailedFile( + file, thumbnailWidth, thumbnailHeight, + thumbnailMimeType, thumbnail); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java index fed34fe4c..5e27494c0 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OutgoingFileTransferJabberImpl.java @@ -8,9 +8,16 @@ import java.io.*; +import net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.util.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.*; import org.jivesoftware.smackx.filetransfer.*; +import org.jivesoftware.smackx.packet.*; /** * The Jabber protocol extension of the AbstractFileTransfer. @@ -19,35 +26,73 @@ */ public class OutgoingFileTransferJabberImpl extends AbstractFileTransfer + implements PacketInterceptor { - private String id; + private final Logger logger + = Logger.getLogger(OutgoingFileTransferJabberImpl.class); - private Contact receiver; + private final String id; - private File file; + private final Contact receiver; + + private final File file; + + private ThumbnailElement thumbnailElement; + + private final ThumbnailRequestListener thumbnailRequestListener + = new ThumbnailRequestListener(); /** * The jabber outgoing file transfer. */ - private OutgoingFileTransfer jabberTransfer; + private final OutgoingFileTransfer jabberTransfer; + + private final ProtocolProviderServiceJabberImpl protocolProvider; /** * Creates an OutgoingFileTransferJabberImpl by specifying the - * Jabber transfer object. - * + * receiver contact, the file, the jabberTransfer, + * that would be used to send the file through Jabber and the + * protocolProvider. + * + * @param receiver the destination contact + * @param file the file to send * @param jabberTransfer the Jabber transfer object, containing all transfer * information + * @param protocolProvider the parent protocol provider */ - public OutgoingFileTransferJabberImpl( Contact receiver, - File file, - OutgoingFileTransfer jabberTransfer) + public OutgoingFileTransferJabberImpl( + Contact receiver, + File file, + OutgoingFileTransfer jabberTransfer, + ProtocolProviderServiceJabberImpl protocolProvider) { - this.jabberTransfer = jabberTransfer; this.receiver = receiver; this.file = file; + this.jabberTransfer = jabberTransfer; + this.protocolProvider = protocolProvider; - this.id = String.valueOf( System.currentTimeMillis()) + // Create the identifier of this file transfer that is used from the + // history and the user interface to track this transfer. + this.id = String.valueOf(System.currentTimeMillis()) + String.valueOf(hashCode()); + + // Add this outgoing transfer as a packet interceptor in + // order to manage thumbnails. + if (file instanceof ThumbnailedFile + && ((ThumbnailedFile) file).getThumbnailData() != null + && ((ThumbnailedFile) file).getThumbnailData().length > 0) + { + if (protocolProvider.isFeatureListSupported( + protocolProvider.getFullJid(receiver), + new String[]{"urn:xmpp:thumbs:0", + "urn:xmpp:bob"})) + { + protocolProvider.getConnection().addPacketWriterInterceptor( + this, + new IQTypeFilter(IQ.Type.SET)); + } + } } /** @@ -78,10 +123,11 @@ public int getDirection() } /** - * The file we are sending. - * @return the file. + * Returns the local file that is being transferred or to which we transfer. + * + * @return the file */ - public File getFile() + public File getLocalFile() { return file; } @@ -103,4 +149,115 @@ public String getID() { return id; } + + /** + * Removes previously added thumbnail request listener. + */ + public void removeThumbnailRequestListener() + { + protocolProvider.getConnection() + .removePacketListener(thumbnailRequestListener); + } + + /** + * Listens for all StreamInitiation packets and adds a thumbnail + * to them if a thumbnailed file is supported. + * + * @see PacketInterceptor#interceptPacket(Packet) + */ + public void interceptPacket(Packet packet) + { + if (!(packet instanceof StreamInitiation)) + return; + + // If our file is not a thumbnailed file we have nothing to do here. + if (!(file instanceof ThumbnailedFile)) + return; + + logger.debug("File transfer packet intercepted" + + " in order to add thumbnail."); + + StreamInitiation fileTransferPacket = (StreamInitiation) packet; + + ThumbnailedFile thumbnailedFile = (ThumbnailedFile) file; + + if (jabberTransfer.getStreamID() + .equals(fileTransferPacket.getSessionID())) + { + StreamInitiation.File file = fileTransferPacket.getFile(); + + thumbnailElement = new ThumbnailElement( + StringUtils.parseServer(fileTransferPacket.getTo()), + thumbnailedFile.getThumbnailData(), + thumbnailedFile.getThumbnailMimeType(), + thumbnailedFile.getThumbnailWidth(), + thumbnailedFile.getThumbnailHeight()); + + FileElement fileElement = new FileElement(file, thumbnailElement); + + fileTransferPacket.setFile(fileElement); + + logger.debug("The file transfer packet with thumbnail: " + + fileTransferPacket.toXML()); + + // Add the request listener in order to listen for requests coming + // for the advertised thumbnail. + if (protocolProvider.getConnection() != null) + { + protocolProvider.getConnection().addPacketListener( + thumbnailRequestListener, + new AndFilter( new PacketTypeFilter(IQ.class), + new IQTypeFilter(IQ.Type.GET))); + } + } + // Remove this packet interceptor after we're done. + protocolProvider.getConnection().removePacketWriterInterceptor(this); + } + + /** + * The ThumbnailRequestListener listens for events triggered by + * the reception of a ThumbnailIQ packet. The packet is examined + * and a ThumbnailIQ is created to respond to the thumbnail + * request received. + */ + private class ThumbnailRequestListener implements PacketListener + { + public void processPacket(Packet packet) + { + // If this is not an IQ packet, we're not interested. + if (!(packet instanceof ThumbnailIQ)) + return; + + ThumbnailIQ thumbnailIQ = (ThumbnailIQ) packet; + + if (thumbnailIQ.getCid() != null + && thumbnailIQ.getCid().equals(thumbnailElement.getCid())) + { + ThumbnailedFile thumbnailedFile = (ThumbnailedFile) file; + + ThumbnailIQ thumbnailResponse = new ThumbnailIQ( + thumbnailIQ.getTo(), + thumbnailIQ.getFrom(), + thumbnailIQ.getCid(), + thumbnailedFile.getThumbnailMimeType(), + thumbnailedFile.getThumbnailData(), + IQ.Type.RESULT); + + logger.debug("Send thumbnail response to the receiver: " + + thumbnailResponse.toXML()); + + protocolProvider.getConnection() + .sendPacket(thumbnailResponse); + } + else + { + // RETURN + } + + if (protocolProvider.getConnection() != null) + { + protocolProvider.getConnection().removePacketListener(this); + } + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 4b36bf050..13df3e73a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -16,8 +16,10 @@ import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; +import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.util.*; import org.jivesoftware.smackx.*; +import org.jivesoftware.smackx.packet.*; /** * An implementation of the protocol provider service over the Jabber protocol @@ -25,6 +27,7 @@ * @author Damian Minkov * @author Symphorien Wanko * @author Lubomir Marinov + * @author Yana Stamcheva */ public class ProtocolProviderServiceJabberImpl extends AbstractProtocolProviderService @@ -427,15 +430,37 @@ private synchronized void connectAndLogin(SecurityAuthority authority, } // we setup supported features + // List of features that smack already supports: + // http://jabber.org/protocol/xhtml-im + // http://jabber.org/protocol/muc + // http://jabber.org/protocol/commands + // http://jabber.org/protocol/chatstates + // http://jabber.org/protocol/si/profile/file-transfer + // http://jabber.org/protocol/si + // http://jabber.org/protocol/bytestreams + // http://jabber.org/protocol/ibb if (getRegistrationState() == RegistrationState.REGISTERED) { discoveryManager = ServiceDiscoveryManager. getInstanceFor(connection); + ServiceDiscoveryManager.setIdentityName("sip-comm"); - ServiceDiscoveryManager.setIdentityType("registered"); + ServiceDiscoveryManager.setIdentityType("pc"); Iterator it = supportedFeatures.iterator(); + + // Remove features supported by smack, but not supported in + // SIP Communicator. + discoveryManager.removeFeature( + "http://jabber.org/protocol/commands"); + + // Add features the SIP Communicator supports in plus of smack. while (it.hasNext()) - discoveryManager.addFeature(it.next()); + { + String feature = it.next(); + + if (!discoveryManager.includesFeature(feature)) + discoveryManager.addFeature(feature); + } } } @@ -547,7 +572,6 @@ protected void initialize(String screenname, // presence, if someone knows // supportedFeatures.add(_PRESENCE_); - //register it once again for those that simply need presence supportedOperationSets.put( OperationSetPresence.class.getName(), persistentPresence); @@ -564,11 +588,8 @@ protected void initialize(String screenname, OperationSetBasicInstantMessaging.class.getName(), basicInstantMessaging); - // TODO: add the feature, if any, corresponding to IM if someone - // knows - // supportedFeatures.add(_IM_); - //XHTML-IM - supportedFeatures.add("http://jabber.org/protocol/xhtml-im"); + // The http://jabber.org/protocol/xhtml-im feature is included + // already in smack. //initialize the Whiteboard operation set OperationSetWhiteboardingJabberImpl whiteboard = @@ -584,7 +605,9 @@ protected void initialize(String screenname, supportedOperationSets.put( OperationSetTypingNotifications.class.getName(), typingNotifications); - supportedFeatures.add("http://jabber.org/protocol/chatstates"); + + // The http://jabber.org/protocol/chatstates feature implemented in + // OperationSetTypingNotifications is included already in smack. //initialize the multi user chat operation set OperationSetMultiUserChat multiUserChat = @@ -619,11 +642,30 @@ protected void initialize(String screenname, OperationSetFileTransfer.class.getName(), fileTransfer); + // Include features we're supporting in plus of the four that + // included by smack itself: + // http://jabber.org/protocol/si/profile/file-transfer + // http://jabber.org/protocol/si + // http://jabber.org/protocol/bytestreams + // http://jabber.org/protocol/ibb + supportedFeatures.add("urn:xmpp:thumbs:0"); + supportedFeatures.add("urn:xmpp:bob"); + + // initialize the thumbnailed file factory operation set + OperationSetThumbnailedFileFactory thumbnailFactory + = new OperationSetThumbnailedFileFactoryImpl(); + + supportedOperationSets.put( + OperationSetThumbnailedFileFactory.class.getName(), + thumbnailFactory); + // TODO: this is the "main" feature to advertise when a client // support muc. We have to add some features for - // specific functionnality we support in muc. + // specific functionality we support in muc. // see http://www.xmpp.org/extensions/xep-0045.html - supportedFeatures.add("http://jabber.org/protocol/muc"); + + // The http://jabber.org/protocol/muc feature is already included in + // smack. supportedFeatures.add("http://jabber.org/protocol/muc#rooms"); supportedFeatures.add("http://jabber.org/protocol/muc#traffic"); @@ -642,6 +684,7 @@ protected void initialize(String screenname, OperationSetBasicTelephony.class.getName(), opSetBasicTelephony); + // Add Jingle features to supported features. supportedFeatures.add("urn:xmpp:jingle:1"); supportedFeatures.add("urn:xmpp:jingle:apps:rtp:1"); supportedFeatures.add("urn:xmpp:jingle:apps:rtp:audio"); @@ -727,7 +770,8 @@ private class JabberConnectionListener public void connectionClosed() { OperationSetPersistentPresenceJabberImpl opSetPersPresence = - (OperationSetPersistentPresenceJabberImpl) getOperationSet(OperationSetPersistentPresence.class); + (OperationSetPersistentPresenceJabberImpl) + getOperationSet(OperationSetPersistentPresence.class); opSetPersPresence.fireProviderStatusChangeEvent( opSetPersPresence.getPresenceStatus(), @@ -799,4 +843,54 @@ JabberStatusEnum getJabberStatusEnum() { return jabberStatusEnum; } + + /** + * Checks if the given list of features is supported by the given + * jabber id. + * @param jid the jabber id for which to check + * @param features the list of features to check for + * @return true if the list of features is supported, otherwise + * returns false + */ + boolean isFeatureListSupported(String jid, String[] features) + { + boolean isFeatureListSupported = true; + + ServiceDiscoveryManager disco = ServiceDiscoveryManager + .getInstanceFor(getConnection()); + try + { + DiscoverInfo featureInfo = disco.discoverInfo(jid); + + for (String feature : features) + { + if (!featureInfo.containsFeature(feature)) + { + // If one is not supported we return false and don't check + // the others. + isFeatureListSupported = false; + break; + } + } + } + catch (XMPPException e) + { + logger.debug("Failed to discover info.", e); + } + + return isFeatureListSupported; + } + + /** + * Returns the full jabber id (jid) corresponding to the given contact. + * @param contact the contact, for which we're looking for a jid + * @return the jid + */ + String getFullJid(Contact contact) + { + Roster roster = getConnection().getRoster(); + Presence presence = roster.getPresence(contact.getAddress()); + + return presence.getFrom(); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ThumbnailedFile.java b/src/net/java/sip/communicator/impl/protocol/jabber/ThumbnailedFile.java new file mode 100644 index 000000000..7f89776cc --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ThumbnailedFile.java @@ -0,0 +1,86 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.io.*; + +/** + * A ThumbnailedFile is a file with a thumbnail. + * + * @author Yana Stamcheva + */ +public class ThumbnailedFile + extends File +{ + private final int thumbnailWidth; + + private final int thumbnailHeight; + + private final String thumbnailMimeType; + + private final byte[] thumbnail; + + /** + * Creates a ThumbnailedFile, by specifying the base file, + * the thumbnailWidth and thumbnailHeight, the + * thumbnailMimeType and the thumbnail itself. + * @param file the base file + * @param thumbnailWidth the width of the thumbnail + * @param thumbnailHeight the height of the thumbnail + * @param thumbnailMimeType the mime type + * @param thumbnail the thumbnail + */ + public ThumbnailedFile( File file, + int thumbnailWidth, + int thumbnailHeight, + String thumbnailMimeType, + byte[] thumbnail) + { + super(file.getPath()); + + this.thumbnailWidth = thumbnailWidth; + this.thumbnailHeight = thumbnailHeight; + this.thumbnailMimeType = thumbnailMimeType; + this.thumbnail = thumbnail; + } + + /** + * Returns the thumbnail of this file. + * @return the thumbnail of this file + */ + public byte[] getThumbnailData() + { + return thumbnail; + } + + /** + * Returns the thumbnail width. + * @return the thumbnail width + */ + public int getThumbnailWidth() + { + return thumbnailWidth; + } + + /** + * Returns the thumbnail height. + * @return the thumbnail height + */ + public int getThumbnailHeight() + { + return thumbnailHeight; + } + + /** + * Returns the thumbnail mime type. + * @return the thumbnail mime type + */ + public String getThumbnailMimeType() + { + return thumbnailMimeType; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/geolocation/GeolocationPacketExtension.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/geolocation/GeolocationPacketExtension.java index 9dfd466e4..815e5b463 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/geolocation/GeolocationPacketExtension.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/geolocation/GeolocationPacketExtension.java @@ -241,7 +241,7 @@ public class GeolocationPacketExtension private String timestamp = null; /** - * Returns the XML reppresentation of the PacketExtension. + * Returns the XML representation of the PacketExtension. * * @return the packet extension as XML. * @todo Implement this org.jivesoftware.smack.packet.PacketExtension diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/FileElement.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/FileElement.java new file mode 100644 index 000000000..a1b2471ad --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/FileElement.java @@ -0,0 +1,269 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.*; +import org.jivesoftware.smackx.packet.*; +import org.jivesoftware.smackx.packet.StreamInitiation.*; +import org.jivesoftware.smackx.provider.*; +import org.xmlpull.v1.*; + +/** + * The FileElement extends the smackx StreamInitiation.File + * in order to provide a file that supports thumbnails. + * + * @author Yana Stamcheva + */ +public class FileElement + extends File + implements IQProvider +{ + private ThumbnailElement thumbnail; + + /** + * The element name of this IQProvider. + */ + public static final String ELEMENT_NAME = "si"; + + /** + * The namespace of this IQProvider. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/si"; + + /** + * An empty constructor used to initialize this class as an + * IQProvider. + */ + public FileElement() + { + this("", 0); + } + + /** + * Creates a FileElement by specifying a base file and a thumbnail + * to extend it with. + * + * @param baseFile the file used as a base + * @param thumbnail the thumbnail to add + */ + public FileElement(File baseFile, ThumbnailElement thumbnail) + { + this(baseFile.getName(), baseFile.getSize()); + + this.thumbnail = thumbnail; + } + + /** + * Creates a FileElement by specifying the name and the size of the + * file. + * + * @param name the name of the file + * @param size the size of the file + */ + public FileElement(String name, long size) + { + super(name, size); + } + + /** + * Represents this FileElement in an XML. + * + * @see File#toXML() + */ + public String toXML() + { + StringBuilder buffer = new StringBuilder(); + + buffer.append("<").append(getElementName()).append(" xmlns=\"") + .append(getNamespace()).append("\" "); + + if (getName() != null) + { + buffer.append("name=\"").append( + StringUtils.escapeForXML(getName())).append("\" "); + } + + if (getSize() > 0) + { + buffer.append("size=\"").append(getSize()).append("\" "); + } + + if (getDate() != null) + { + buffer.append("date=\"").append( + DelayInformation.UTC_FORMAT + .format(this.getDate())).append("\" "); + } + + if (getHash() != null) + { + buffer.append("hash=\"").append(getHash()).append("\" "); + } + + if ((this.getDesc() != null && getDesc().length() > 0) + || isRanged() + || thumbnail != null) + { + buffer.append(">"); + + if (getDesc() != null && getDesc().length() > 0) + { + buffer.append("").append( + StringUtils.escapeForXML(getDesc())).append(""); + } + + if (isRanged()) + { + buffer.append(""); + } + + if (thumbnail != null) + { + buffer.append(thumbnail.toXML()); + } + + buffer.append(""); + } + else + { + buffer.append("/>"); + } + + return buffer.toString(); + } + + /** + * Returns the ThumbnailElement contained in this + * FileElement. + * @return the ThumbnailElement contained in this + * FileElement + */ + public ThumbnailElement getThumbnailElement() + { + return thumbnail; + } + + /** + * Sets the given thumbnail to this FileElement. + * @param thumbnail the ThumbnailElement to set + */ + public void setThumbnailElement(ThumbnailElement thumbnail) + { + this.thumbnail = thumbnail; + } + + /** + * Parses the given parser in order to create a + * FileElement from it. + * @param parser the parser to parse + * @see IQProvider#parseIQ(XmlPullParser) + */ + public IQ parseIQ(final XmlPullParser parser) + throws Exception + { + boolean done = false; + + // si + String id = parser.getAttributeValue("", "id"); + String mimeType = parser.getAttributeValue("", "mime-type"); + StreamInitiation initiation = new StreamInitiation(); + + // file + String name = null; + String size = null; + String hash = null; + String date = null; + String desc = null; + ThumbnailElement thumbnail = null; + boolean isRanged = false; + + // feature + DataForm form = null; + DataFormProvider dataFormProvider = new DataFormProvider(); + + int eventType; + String elementName; + String namespace; + + while (!done) + { + eventType = parser.next(); + elementName = parser.getName(); + namespace = parser.getNamespace(); + + if (eventType == XmlPullParser.START_TAG) + { + if (elementName.equals("file")) + { + name = parser.getAttributeValue("", "name"); + size = parser.getAttributeValue("", "size"); + hash = parser.getAttributeValue("", "hash"); + date = parser.getAttributeValue("", "date"); + } + else if (elementName.equals("desc")) + { + desc = parser.nextText(); + } + else if (elementName.equals("range")) + { + isRanged = true; + } + else if (elementName.equals("x") + && namespace.equals("jabber:x:data")) + { + form = (DataForm) dataFormProvider.parseExtension(parser); + } + else if (elementName.equals("thumbnail")) + { + thumbnail = new ThumbnailElement(parser.getText()); + } + } + else if (eventType == XmlPullParser.END_TAG) + { + if (elementName.equals("si")) + done = true; + else if (elementName.equals("file")) + { + long fileSize = 0; + + if(size != null && size.trim().length() !=0) + { + try + { + fileSize = Long.parseLong(size); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + } + + FileElement file = new FileElement(name, fileSize); + file.setHash(hash); + + if (date != null) + file.setDate(DelayInformation.UTC_FORMAT.parse(date)); + + if (thumbnail != null) + file.setThumbnailElement(thumbnail); + + file.setDesc(desc); + file.setRanged(isRanged); + initiation.setFile(file); + } + } + } + + initiation.setSesssionID(id); + initiation.setMimeType(mimeType); + initiation.setFeatureNegotiationForm(form); + + return initiation; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailElement.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailElement.java new file mode 100644 index 000000000..e826a55aa --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailElement.java @@ -0,0 +1,298 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail; + +import java.io.*; +import java.security.*; + +import javax.xml.parsers.*; + +import org.w3c.dom.*; + +import net.java.sip.communicator.util.*; + +/** + * The ThumbnailElement represents a "thumbnail" XML element, that is + * contained in the file element, we're sending to notify for a file transfer. + * The ThumbnailElement's role is to advertise a thumbnail. + * + * @author Yana Stamcheva + */ +public class ThumbnailElement +{ + private static final Logger logger + = Logger.getLogger(ThumbnailElement.class); + + /** + * The name of the XML element used for transport of thumbnail parameters. + */ + public static final String ELEMENT_NAME = "thumbnail"; + + /** + * The names XMPP space that the thumbnail elements belong to. + */ + public static final String NAMESPACE = "urn:xmpp:thumbs:0"; + + /** + * The name of the thumbnail attribute "cid". + */ + public final static String CID = "cid"; + + /** + * The name of the thumbnail attribute "mime-type". + */ + public final static String MIME_TYPE = "mime-type"; + + /** + * The name of the thumbnail attribute "width". + */ + public final static String WIDTH = "width"; + + /** + * The name of the thumbnail attribute "height". + */ + public final static String HEIGHT = "height"; + + private String cid; + + private String mimeType; + + private int width; + + private int height; + + /** + * Creates a ThumbnailPacketExtension by specifying all extension + * attributes. + * + * @param serverAddress the Jabber address of the destination contact + * @param thumbnailData the byte array containing the thumbnail data + * @param mimeType the mime type attribute + * @param width the width of the thumbnail + * @param height the height of the thumbnail + */ + public ThumbnailElement(String serverAddress, + byte[] thumbnailData, + String mimeType, + int width, + int height) + { + this.cid = createCid(serverAddress, thumbnailData); + this.mimeType = mimeType; + this.width = width; + this.height = height; + } + + /** + * Creates a ThumbnailElement by parsing the given xml. + * + * @param xml the XML from which we obtain the needed information to create + * this ThumbnailElement + */ + public ThumbnailElement(String xml) + { + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + + DocumentBuilder builder; + try + { + builder = factory.newDocumentBuilder(); + InputStream in = new ByteArrayInputStream (xml.getBytes()); + Document doc = builder.parse(in); + + Element e = doc.getDocumentElement(); + String elementName = e.getNodeName(); + + if (elementName.equals (ELEMENT_NAME)) + { + this.setCid(e.getAttribute (CID)); + this.setMimeType(e.getAttribute(MIME_TYPE)); + this.setHeight(Integer.parseInt(e.getAttribute(HEIGHT))); + this.setHeight(Integer.parseInt(e.getAttribute(WIDTH))); + } + else + logger.debug ("Element name unknown!"); + } + catch (ParserConfigurationException ex) + { + logger.debug ("Problem parsing Thumbnail Element : " + xml, ex); + } + catch (IOException ex) + { + logger.debug ("Problem parsing Thumbnail Element : " + xml, ex); + } + catch (Exception ex) + { + logger.debug ("Problem parsing Thumbnail Element : " + xml, ex); + } + } + + /** + * Returns the XML representation of this PacketExtension. + * + * @return the packet extension as XML. + */ + public String toXML() + { + StringBuffer buf = new StringBuffer(); + + // open element + buf.append("<").append(ELEMENT_NAME). + append(" xmlns=\"").append(NAMESPACE).append("\""); + + // adding thumbnail parameters + buf = addXmlAttribute(buf, CID, this.getCid()); + buf = addXmlAttribute(buf, MIME_TYPE, this.getMimeType()); + buf = addXmlIntAttribute(buf, WIDTH, this.getWidth()); + buf = addXmlIntAttribute(buf, HEIGHT, this.getWidth()); + + // close element + buf.append("/>"); + + return buf.toString(); + } + + /** + * Returns the Content-ID, corresponding to this ThumbnailElement. + * @return the Content-ID, corresponding to this ThumbnailElement + */ + public String getCid() + { + return cid; + } + + /** + * Returns the mime type of this ThumbnailElement. + * @return the mime type of this ThumbnailElement + */ + public String getMimeType() + { + return mimeType; + } + + /** + * Returns the width of this ThumbnailElement. + * @return the width of this ThumbnailElement + */ + public int getWidth() + { + return width; + } + + /** + * Returns the height of this ThumbnailElement. + * @return the height of this ThumbnailElement + */ + public int getHeight() + { + return height; + } + + /** + * Sets the content-ID of this ThumbnailElement. + * @param cid the content-ID to set + */ + public void setCid(String cid) + { + this.cid = cid; + } + + /** + * Sets the mime type of the thumbnail. + * @param mimeType the mime type of the thumbnail + */ + public void setMimeType(String mimeType) + { + this.mimeType = mimeType; + } + + /** + * Sets the width of the thumbnail + * @param width the width of the thumbnail + */ + public void setWidth(int width) + { + this.width = width; + } + + /** + * Sets the height of the thumbnail + * @param height the height of the thumbnail + */ + public void setHeight(int height) + { + this.height = height; + } + + /** + * Creates the XML String corresponding to the specified attribute + * and value and adds them to the buff StringBuffer. + * + * @param buff the StringBuffer to add the attribute and value to. + * @param attrName the name of the thumbnail attribute that we're adding. + * @param attrValue the value of the attribute we're adding to the XML + * buffer. + * @return the StringBuffer that we've added the attribute and its + * value to. + */ + private StringBuffer addXmlAttribute( StringBuffer buff, + String attrName, + String attrValue) + { + buff.append(" " + attrName + "=\"").append(attrValue).append("\""); + + return buff; + } + + /** + * Creates the XML String corresponding to the specified attribute + * and value and adds them to the buff StringBuffer. + * + * @param buff the StringBuffer to add the attribute and value to. + * @param attrName the name of the thumbnail attribute that we're adding. + * @param attrValue the value of the attribute we're adding to the XML + * buffer. + * @return the StringBuffer that we've added the attribute and its + * value to. + */ + private StringBuffer addXmlIntAttribute(StringBuffer buff, + String attrName, + int attrValue) + { + + return addXmlAttribute(buff, attrName, String.valueOf(attrValue)); + } + + /** + * Creates the cid attrubte value for the given contactJabberAddress + * and thumbnailData. + * + * @param serverAddress the Jabber server address + * @param thumbnailData the byte array containing the data + * @return the cid attrubte value for the thumbnail extension + */ + private String createCid( String serverAddress, + byte[] thumbnailData) + { + try + { + return "sha1+" + Sha1Crypto.encode(thumbnailData) + + "@" + serverAddress; + } + catch (NoSuchAlgorithmException e) + { + logger.debug("Failed to encode the thumbnail in SHA-1.", e); + } + catch (UnsupportedEncodingException e) + { + logger.debug("Failed to encode the thumbnail in SHA-1.", e); + } + + return null; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailIQ.java b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailIQ.java new file mode 100644 index 000000000..aa1a622b6 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/extensions/thumbnail/ThumbnailIQ.java @@ -0,0 +1,171 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber.extensions.thumbnail; + +import net.java.sip.communicator.util.*; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.xmlpull.v1.*; + +/** + * The ThumbnailIQ is an IQ packet that is meant to be used for + * thumbnail requests and responses. + * + * @author Yana Stamcheva + */ +public class ThumbnailIQ + extends IQ + implements IQProvider +{ + /** + * The names XMPP space that the thumbnail elements belong to. + */ + public static final String NAMESPACE = "urn:xmpp:bob"; + + /** + * The name of the "data" element. + */ + public static final String ELEMENT_NAME = "data"; + + /** + * The name of the thumbnail attribute "cid". + */ + public final static String CID = "cid"; + + /** + * The name of the thumbnail attribute "mime-type". + */ + public final static String TYPE = "type"; + + private String cid; + + private String mimeType; + + private byte[] data; + + /** + * An empty constructor used to initialize this class as an + * IQProvier. + */ + public ThumbnailIQ() {} + + /** + * Creates a ThumbnailIQ packet, by specifying the source, the + * destination, the content-ID and the type of this packet. The type could + * be one of the types defined in IQ.Type. + * + * @param from the source of the packet + * @param to the destination of the packet + * @param cid the content-ID used to identify this packet in the destination + * @param type the of the packet, which could be one of the types defined + * in IQ.Type + */ + public ThumbnailIQ(String from, String to, String cid, Type type) + { + this.cid = cid; + + this.setFrom(from); + this.setTo(to); + this.setType(type); + } + + /** + * Creates a ThumbnailIQ packet, by specifying the source, the + * destination, the content-ID, the type of data and the data of the + * thumbnail. We also precise the type of the packet to create. + * + * @param from the source of the packet + * @param to the destination of the packet + * @param cid the content-ID used to identify this packet in the destination + * @param mimeType the type of the data passed + * @param data the data of the thumbnail + * @param type the of the packet, which could be one of the types defined + * in IQ.Type + */ + public ThumbnailIQ( String from, String to, String cid, + String mimeType, byte[] data, Type type) + { + this(from, to, cid, type); + + this.data = data; + this.mimeType = mimeType; + } + + /** + * Parses the given XmlPullParser into a ThumbnailIQ packet and + * returns it. + * @see IQProvider#parseIQ(XmlPullParser) + */ + public IQ parseIQ(XmlPullParser parser) throws Exception + { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + + if (elementName.equals(ELEMENT_NAME) + && namespace.equals(NAMESPACE)) + { + this.cid = parser.getAttributeValue("", CID); + this.mimeType = parser.getAttributeValue("", TYPE); + } + + int eventType = parser.next(); + + if (eventType == XmlPullParser.TEXT) + { + this.data = Base64.decode(parser.getText()); + } + + return this; + } + + /** + * Returns the xml representing the data element in this IQ packet. + */ + public String getChildElementXML() + { + StringBuffer buf = new StringBuffer(); + + // open extension + buf.append("<").append(ELEMENT_NAME) + .append(" xmlns=\"").append(NAMESPACE).append("\"") + .append(" " + CID).append("=\"").append(cid).append("\""); + + if (mimeType != null) + buf.append(" " + TYPE).append("=\"").append(mimeType).append("\">"); + else + buf.append(">"); + + if (data != null) + { + byte[] encodedData = Base64.encode(data); + buf.append(new String(encodedData)); + } + + buf.append(""); + + return buf.toString(); + } + + /** + * Returns the content-ID of this thumbnail packet. + * @return the content-ID of this thumbnail packet + */ + public String getCid() + { + return cid; + } + + /** + * Returns the data of the thumbnail. + * @return the data of the thumbnail + */ + public byte[] getData() + { + return data; + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index dfda50fb0..4faeb4338 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -20,6 +20,7 @@ Import-Package: org.osgi.framework, org.jivesoftware.smackx.jingle.packet, org.jivesoftware.smackx.jingle.provider, org.jivesoftware.smackx.filetransfer, + org.jivesoftware.smackx.provider, net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.resources, net.java.sip.communicator.util, diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockFileTransferImpl.java b/src/net/java/sip/communicator/impl/protocol/mock/MockFileTransferImpl.java index b78ec9cdb..982dcc2ed 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockFileTransferImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockFileTransferImpl.java @@ -18,8 +18,8 @@ public class MockFileTransferImpl extends AbstractFileTransfer { private String id = null; - private int direction; - private File file = null; + private final int direction; + private final File file; private Contact contact = null; public MockFileTransferImpl(Contact c, File file, String id, int direction) @@ -61,7 +61,7 @@ public int getDirection() return direction; } - public File getFile() + public File getLocalFile() { return file; } diff --git a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetFileTransfer.java b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetFileTransfer.java index 54eef5600..82170da66 100644 --- a/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetFileTransfer.java +++ b/src/net/java/sip/communicator/impl/protocol/mock/MockOperationSetFileTransfer.java @@ -132,7 +132,11 @@ public FileTransfer acceptFile(File file) public void rejectFile() { + } + public byte[] getThumbnail() + { + return null; } }, requestDate)); } diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java index 058b13e3d..cff5e2eea 100644 --- a/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/ssh/FileTransferSSHImpl.java @@ -61,7 +61,7 @@ public int getDirection() return IN; } - public File getFile() + public File getLocalFile() { return null; } diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java index 6d78ad517..fa8f15fdf 100644 --- a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java @@ -155,36 +155,49 @@ private boolean wrappedMessage( String message, ContactSSH sshContact) { - if(message.startsWith("/upload")) { int firstSpace = message.indexOf(' '); - sshContact.getFileTransferOperationSet().sendFile( - sshContact, - null, - message.substring(message.indexOf(' ', firstSpace+1) + 1), - message.substring( - firstSpace+1, - message.indexOf(' ', firstSpace+1))); - + + try + { + sshContact.getFileTransferOperationSet().sendFile( + sshContact, + null, + message.substring(message.indexOf(' ', firstSpace+1) + 1), + message.substring( + firstSpace+1, + message.indexOf(' ', firstSpace+1))); + } + catch (Exception e) + { + e.printStackTrace(); + } + return true; } else if(message.startsWith("/download")) { int firstSpace = message.indexOf(' '); - sshContact.getFileTransferOperationSet().sendFile( + + try + { + sshContact.getFileTransferOperationSet().sendFile( null, sshContact, message.substring(firstSpace+1, message.indexOf(' ', firstSpace+1)), message.substring(message.indexOf(' ', firstSpace+1) + 1)); - + } + catch(Exception e) + { + e.printStackTrace(); + } return true; } - return false; } - + /** * In case the to Contact corresponds to another ssh * protocol provider registered with SIP Communicator, we deliver diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java index 828fcd67f..2799bd3ff 100644 --- a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java @@ -14,6 +14,8 @@ import java.io.*; import java.util.*; +import javax.management.*; + import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java index 0f3f2d9f0..b346d7e97 100644 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/FileTransferImpl.java @@ -79,7 +79,7 @@ public int getDirection() * * @return the file */ - public File getFile() + public File getLocalFile() { return file; } diff --git a/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java b/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java index e7d552690..09b7dbcc3 100644 --- a/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/yahoo/IncomingFileTransferRequestYahooImpl.java @@ -174,4 +174,14 @@ public Date getDate() { return date; } + + /** + * Returns the thumbnail contained in this request. + * + * @return the thumbnail contained in this request + */ + public byte[] getThumbnail() + { + return null; + } } diff --git a/src/net/java/sip/communicator/service/protocol/AbstractFileTransfer.java b/src/net/java/sip/communicator/service/protocol/AbstractFileTransfer.java index b61a77875..59fbbd5f9 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractFileTransfer.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractFileTransfer.java @@ -124,8 +124,20 @@ public int getStatus() /** * Notifies all status listeners that a new * FileTransferStatusChangeEvent occured. + * @param newStatus the new status */ public void fireStatusChangeEvent(int newStatus) + { + this.fireStatusChangeEvent(newStatus, null); + } + + /** + * Notifies all status listeners that a new + * FileTransferStatusChangeEvent occured. + * @param newStatus the new status + * @param reason the reason of the status change + */ + public void fireStatusChangeEvent(int newStatus, String reason) { // ignore if status is the same if(this.status == newStatus) @@ -139,7 +151,8 @@ public void fireStatusChangeEvent(int newStatus) } FileTransferStatusChangeEvent statusEvent - = new FileTransferStatusChangeEvent(this, status, newStatus); + = new FileTransferStatusChangeEvent( + this, status, newStatus, reason); // Updates the status. this.status = newStatus; diff --git a/src/net/java/sip/communicator/service/protocol/FileTransfer.java b/src/net/java/sip/communicator/service/protocol/FileTransfer.java index abb070bee..a821354b2 100644 --- a/src/net/java/sip/communicator/service/protocol/FileTransfer.java +++ b/src/net/java/sip/communicator/service/protocol/FileTransfer.java @@ -50,11 +50,11 @@ public interface FileTransfer public int getDirection(); /** - * Returns the file that is transfered. + * Returns the local file that is being transferred or to which we transfer. * * @return the file */ - public File getFile(); + public File getLocalFile(); /** * Returns the contact that we are transfering files with. diff --git a/src/net/java/sip/communicator/service/protocol/IncomingFileTransferRequest.java b/src/net/java/sip/communicator/service/protocol/IncomingFileTransferRequest.java index 81f027dcc..6f1a35ddf 100644 --- a/src/net/java/sip/communicator/service/protocol/IncomingFileTransferRequest.java +++ b/src/net/java/sip/communicator/service/protocol/IncomingFileTransferRequest.java @@ -62,7 +62,7 @@ public interface IncomingFileTransferRequest /** * Function called to accept and receive the file. - * + * * @param file the file to accept * @return the FileTransfer object managing the transfer */ @@ -72,4 +72,11 @@ public interface IncomingFileTransferRequest * Function called to refuse the file. */ public void rejectFile(); + + /** + * Returns the thumbnail contained in this request. + * + * @return the thumbnail contained in this request + */ + public byte[] getThumbnail(); } diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java b/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java index 0c02846ec..975264d84 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java @@ -34,11 +34,14 @@ public interface OperationSetFileTransfer * or connected * @throws IllegalArgumentException if some of the arguments doesn't fit the * protocol requirements + * @throws OperationNotSupportedException if the given contact client or + * server does not support file transfers */ public FileTransfer sendFile( Contact toContact, File file) throws IllegalStateException, - IllegalArgumentException; + IllegalArgumentException, + OperationNotSupportedException; /** * Sends a file transfer request to the given toContact by @@ -56,13 +59,16 @@ public FileTransfer sendFile( Contact toContact, * or connected * @throws IllegalArgumentException if some of the arguments doesn't fit the * protocol requirements + * @throws OperationNotSupportedException if the given contact client or + * server does not support file transfers. */ public FileTransfer sendFile( Contact toContact, Contact fromContact, String remotePath, String localPath) throws IllegalStateException, - IllegalArgumentException; + IllegalArgumentException, + OperationNotSupportedException; /** * Adds the given FileTransferListener that would listen for diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetThumbnailedFileFactory.java b/src/net/java/sip/communicator/service/protocol/OperationSetThumbnailedFileFactory.java new file mode 100644 index 000000000..fb93ea532 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/OperationSetThumbnailedFileFactory.java @@ -0,0 +1,37 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol; + +import java.io.*; + +/** + * The OperationSetThumbnailedFileFactory is meant to be used by + * bundles interested in making files with thumbnails. For example the user + * interface can be interested in sending files with thumbnails through the + * OperationSetFileTransfer. + * + * @author Yana Stamcheva + */ +public interface OperationSetThumbnailedFileFactory + extends OperationSet +{ + /** + * Creates a file, by attaching the thumbnail, given by the details, to it. + * + * @param file the base file + * @param thumbnailWidth the width of the thumbnail + * @param thumbnailHeight the height of the thumbnail + * @param thumbnailMimeType the mime type of the thumbnail + * @param thumbnail the thumbnail data + * @return a file with a thumbnail + */ + public File createFileWithThumbnail(File file, + int thumbnailWidth, + int thumbnailHeight, + String thumbnailMimeType, + byte[] thumbnail); +} diff --git a/src/net/java/sip/communicator/service/protocol/event/FileTransferStatusChangeEvent.java b/src/net/java/sip/communicator/service/protocol/event/FileTransferStatusChangeEvent.java index 36b9a9726..ee5daf3f4 100644 --- a/src/net/java/sip/communicator/service/protocol/event/FileTransferStatusChangeEvent.java +++ b/src/net/java/sip/communicator/service/protocol/event/FileTransferStatusChangeEvent.java @@ -65,6 +65,11 @@ public class FileTransferStatusChangeEvent */ private final int newStatus; + /** + * The reason of this status change. + */ + private String reason; + /** * Creates a FileTransferStatusChangeEvent by specifying the * source fileTransfer, the old transfer status and the new status. @@ -73,15 +78,18 @@ public class FileTransferStatusChangeEvent * change occured * @param oldStatus the old status * @param newStatus the new status + * @param reason the reason of this status change */ public FileTransferStatusChangeEvent( FileTransfer fileTransfer, int oldStatus, - int newStatus) + int newStatus, + String reason) { super(fileTransfer); this.oldStatus = oldStatus; this.newStatus = newStatus; + this.reason = reason; } /** @@ -113,4 +121,13 @@ public int getNewStatus() { return newStatus; } + + /** + * Returns the reason of the status change. + * @return the reason of the status change + */ + public String getReason() + { + return reason; + } } diff --git a/src/net/java/sip/communicator/util/ImageUtils.java b/src/net/java/sip/communicator/util/ImageUtils.java index 11fb319a6..97dd8f961 100644 --- a/src/net/java/sip/communicator/util/ImageUtils.java +++ b/src/net/java/sip/communicator/util/ImageUtils.java @@ -107,6 +107,41 @@ public static Image getScaledRoundedImage(Image image, int width, int height) return destImage; } + public static byte[] getScaledInstanceInBytes( + Image image, int width, int height) + { + byte[] scaledBytes = null; + + BufferedImage scaledImage + = (BufferedImage) getScaledRoundedImage(image, width, height); + + if (scaledImage != null) + { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + + try + { + ImageIO.write(scaledImage, "png", outStream); + scaledBytes = outStream.toByteArray(); + } + catch (IOException e) + { + logger.debug("Could not scale image in bytes.", e); + } + + } + + return scaledBytes; + } + + /** + * Returns a scaled rounded icon from the given image, scaled + * within the given width and height. + * @param image the image to scale + * @param width the maximum width of the scaled icon + * @param height the maximum height of the scaled icon + * @return a scaled rounded icon + */ public static ImageIcon getScaledRoundedIcon(Image image, int width, int height) { diff --git a/src/net/java/sip/communicator/util/Sha1Crypto.java b/src/net/java/sip/communicator/util/Sha1Crypto.java new file mode 100644 index 000000000..692a0755f --- /dev/null +++ b/src/net/java/sip/communicator/util/Sha1Crypto.java @@ -0,0 +1,76 @@ +package net.java.sip.communicator.util; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Sha1Crypto +{ + /** + * Encodes the given text with the SHA-1 algorithm. + * + * @param text the text to encode + * @return the encoded text + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException + */ + public static String encode(String text) + throws NoSuchAlgorithmException, + UnsupportedEncodingException + { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + + byte[] sha1hash; + messageDigest.update(text.getBytes("iso-8859-1"), 0, text.length()); + sha1hash = messageDigest.digest(); + + return convertToHex(sha1hash); + } + + /** + * Encodes the given text with the SHA-1 algorithm. + * + * @param byteArreay the byte array to encode + * @return the encoded text + * @throws NoSuchAlgorithmException + * @throws UnsupportedEncodingException + */ + public static String encode(byte[] byteArray) + throws NoSuchAlgorithmException, + UnsupportedEncodingException + { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + + byte[] sha1hash; + messageDigest.update(byteArray); + sha1hash = messageDigest.digest(); + + return convertToHex(sha1hash); + } + + /** + * Converts the given byte data into Hex string. + * + * @param data the byte array to convert + * @return the Hex string representation of the given byte array + */ + private static String convertToHex(byte[] data) + { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < data.length; i++) + { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do + { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append((char) ('0' + halfbyte)); + else + buf.append((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } + while(two_halfs++ < 1); + } + return buf.toString(); + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/generic/TestOperationSetFileTransfer.java b/test/net/java/sip/communicator/slick/protocol/generic/TestOperationSetFileTransfer.java index 09631dc3f..9ee5dafdd 100644 --- a/test/net/java/sip/communicator/slick/protocol/generic/TestOperationSetFileTransfer.java +++ b/test/net/java/sip/communicator/slick/protocol/generic/TestOperationSetFileTransfer.java @@ -119,7 +119,7 @@ public void testSendAndReceive() = (FileTransferCreatedEvent)senderFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,fileToTransfer); assertEquals("A file transfer status changed - preparing received on send side" @@ -169,7 +169,7 @@ public void testSendAndReceive() = (FileTransferCreatedEvent)receiverFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,receiveFile); receiverStatusListener.waitForEvent(30000, 3); @@ -251,7 +251,7 @@ public void testSenderCancelBeforeAccepted() = (FileTransferCreatedEvent)senderFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,fileToTransfer); assertEquals("A file transfer status changed - preparing received on send side" @@ -361,7 +361,7 @@ public void testReceiverDecline() = (FileTransferCreatedEvent)senderFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,fileToTransfer); assertEquals("A file transfer status changed - preparing received on send side" @@ -475,7 +475,7 @@ public void testReceiverCancelsWhileTransfering() = (FileTransferCreatedEvent)senderFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,fileToTransfer); assertEquals("A file transfer status changed - preparing received on send side" @@ -562,7 +562,7 @@ public void testReceiverCancelsWhileTransfering() = (FileTransferCreatedEvent)receiverFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,receiveFile); // sender @@ -618,7 +618,7 @@ public void testSenderCancelsWhileTransfering() = (FileTransferCreatedEvent)senderFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,fileToTransfer); assertEquals("A file transfer status changed - preparing received on send side" @@ -691,7 +691,7 @@ public void testSenderCancelsWhileTransfering() = (FileTransferCreatedEvent)receiverFTListerner.collectedEvents.get(0); assertEquals("FileTransfer file" - ,fileTransferCreatedEvent.getFileTransfer().getFile() + ,fileTransferCreatedEvent.getFileTransfer().getLocalFile() ,receiveFile); receiverStatusListener.waitForEvent(4000, 3); @@ -920,17 +920,20 @@ public void waitForEvent(long waitFor, int eventsNum) synchronized(this) { - if(collectedEvents.size() > (eventsNum - 1)){ + if(collectedEvents.size() > (eventsNum - 1)) + { logger.trace("Event already received. " + collectedEvents); return; } - try{ + try + { wait(waitFor); if(collectedEvents.size() > (eventsNum - 1)) logger.trace("Received a FileTransferEvent."); else - logger.trace("No FileTransferEvent received for "+waitFor+"ms."); + logger.trace("No FileTransferEvent received for " + + waitFor + "ms."); } catch (InterruptedException ex) {