001/** 002 * 003 * Copyright 2003-2006 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.filetransfer; 018 019import java.io.File; 020import java.io.FileNotFoundException; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.util.concurrent.Callable; 026import java.util.concurrent.ExecutionException; 027import java.util.concurrent.FutureTask; 028import java.util.concurrent.TimeUnit; 029import java.util.concurrent.TimeoutException; 030import java.util.logging.Level; 031import java.util.logging.Logger; 032 033import org.jivesoftware.smack.SmackException; 034import org.jivesoftware.smack.SmackException.NoResponseException; 035import org.jivesoftware.smack.XMPPException.XMPPErrorException; 036 037 038/** 039 * An incoming file transfer is created when the 040 * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)} 041 * method is invoked. It is a file being sent to the local user from another 042 * user on the jabber network. There are two stages of the file transfer to be 043 * concerned with and they can be handled in different ways depending upon the 044 * method that is invoked on this class. 045 * 046 * The first way that a file is received is by calling the 047 * {@link #receiveFile()} method. This method, negotiates the appropriate stream 048 * method and then returns the InputStream to read the file 049 * data from. 050 * 051 * The second way that a file can be received through this class is by invoking 052 * the {@link #receiveFile(File)} method. This method returns immediately and 053 * takes as its parameter a file on the local file system where the file 054 * recieved from the transfer will be put. 055 * 056 * @author Alexander Wenckus 057 */ 058public class IncomingFileTransfer extends FileTransfer { 059 060 private static final Logger LOGGER = Logger.getLogger(IncomingFileTransfer.class.getName()); 061 062 private FileTransferRequest receiveRequest; 063 064 private InputStream inputStream; 065 066 protected IncomingFileTransfer(FileTransferRequest request, 067 FileTransferNegotiator transferNegotiator) { 068 super(request.getRequestor(), request.getStreamID(), transferNegotiator); 069 this.receiveRequest = request; 070 } 071 072 /** 073 * Negotiates the stream method to transfer the file over and then returns 074 * the negotiated stream. 075 * 076 * @return The negotiated InputStream from which to read the data. 077 * @throws SmackException 078 * @throws XMPPErrorException If there is an error in the negotiation process an exception 079 * is thrown. 080 * @throws InterruptedException 081 */ 082 public InputStream receiveFile() throws SmackException, XMPPErrorException, InterruptedException { 083 if (inputStream != null) { 084 throw new IllegalStateException("Transfer already negotiated!"); 085 } 086 087 try { 088 inputStream = negotiateStream(); 089 } 090 catch (XMPPErrorException e) { 091 setException(e); 092 throw e; 093 } 094 095 return inputStream; 096 } 097 098 /** 099 * This method negotiates the stream and then transfer's the file over the negotiated stream. 100 * The transferred file will be saved at the provided location. 101 * 102 * This method will return immediately, file transfer progress can be monitored through several 103 * methods: 104 * 105 * <UL> 106 * <LI>{@link FileTransfer#getStatus()}</LI> 107 * <LI>{@link FileTransfer#getProgress()}</LI> 108 * <LI>{@link FileTransfer#isDone()}</LI> 109 * </UL> 110 * 111 * @param file The location to save the file. 112 * @throws SmackException when the file transfer fails 113 * @throws IOException 114 * @throws IllegalArgumentException This exception is thrown when the the provided file is 115 * either null, or cannot be written to. 116 */ 117 public void receiveFile(final File file) throws SmackException, IOException { 118 if (file == null) { 119 throw new IllegalArgumentException("File cannot be null"); 120 } 121 if (!file.exists()) { 122 file.createNewFile(); 123 } 124 if (!file.canWrite()) { 125 throw new IllegalArgumentException("Cannot write to provided file"); 126 } 127 128 Thread transferThread = new Thread(new Runnable() { 129 @Override 130 public void run() { 131 try { 132 inputStream = negotiateStream(); 133 } 134 catch (Exception e) { 135 setStatus(FileTransfer.Status.error); 136 setException(e); 137 return; 138 } 139 140 OutputStream outputStream = null; 141 try { 142 outputStream = new FileOutputStream(file); 143 setStatus(Status.in_progress); 144 writeToStream(inputStream, outputStream); 145 } 146 catch (FileNotFoundException e) { 147 setStatus(Status.error); 148 setError(Error.bad_file); 149 setException(e); 150 } 151 catch (IOException e) { 152 setStatus(Status.error); 153 setError(Error.stream); 154 setException(e); 155 } 156 157 158 if (getStatus().equals(Status.in_progress)) { 159 setStatus(Status.complete); 160 } 161 if (inputStream != null) { 162 try { 163 inputStream.close(); 164 } catch (IOException e) { 165 LOGGER.log(Level.WARNING, "Closing input stream", e); 166 } 167 } 168 if (outputStream != null) { 169 try { 170 outputStream.close(); 171 } catch (IOException e) { 172 LOGGER.log(Level.WARNING, "Closing output stream", e); 173 } 174 } 175 } 176 }, "File Transfer " + streamID); 177 transferThread.start(); 178 } 179 180 private InputStream negotiateStream() throws SmackException, XMPPErrorException, InterruptedException { 181 setStatus(Status.negotiating_transfer); 182 final StreamNegotiator streamNegotiator = negotiator 183 .selectStreamNegotiator(receiveRequest); 184 setStatus(Status.negotiating_stream); 185 FutureTask<InputStream> streamNegotiatorTask = new FutureTask<>( 186 new Callable<InputStream>() { 187 188 @Override 189 public InputStream call() throws Exception { 190 return streamNegotiator 191 .createIncomingStream(receiveRequest.getStreamInitiation()); 192 } 193 }); 194 streamNegotiatorTask.run(); 195 InputStream inputStream; 196 try { 197 inputStream = streamNegotiatorTask.get(15, TimeUnit.SECONDS); 198 } 199 catch (ExecutionException e) { 200 final Throwable cause = e.getCause(); 201 if (cause instanceof XMPPErrorException) { 202 throw (XMPPErrorException) cause; 203 } 204 if (cause instanceof InterruptedException) { 205 throw (InterruptedException) cause; 206 } 207 if (cause instanceof NoResponseException) { 208 throw (NoResponseException) cause; 209 } 210 if (cause instanceof SmackException) { 211 throw (SmackException) cause; 212 } 213 throw new SmackException("Error in execution", e); 214 } 215 catch (TimeoutException e) { 216 throw new SmackException("Request timed out", e); 217 } 218 finally { 219 streamNegotiatorTask.cancel(true); 220 } 221 setStatus(Status.negotiated); 222 return inputStream; 223 } 224 225 @Override 226 public void cancel() { 227 setStatus(Status.cancelled); 228 } 229 230}