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.XMPPException.XMPPErrorException; 035 036 037/** 038 * An incoming file transfer is created when the 039 * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)} 040 * method is invoked. It is a file being sent to the local user from another 041 * user on the jabber network. There are two stages of the file transfer to be 042 * concerned with and they can be handled in different ways depending upon the 043 * method that is invoked on this class. 044 * <p/> 045 * The first way that a file is recieved is by calling the 046 * {@link #recieveFile()} method. This method, negotiates the appropriate stream 047 * method and then returns the <b><i>InputStream</b></i> to read the file 048 * data from. 049 * <p/> 050 * The second way that a file can be recieved through this class is by invoking 051 * the {@link #recieveFile(File)} method. This method returns immediatly and 052 * takes as its parameter a file on the local file system where the file 053 * recieved from the transfer will be put. 054 * 055 * @author Alexander Wenckus 056 */ 057public class IncomingFileTransfer extends FileTransfer { 058 059 private static final Logger LOGGER = Logger.getLogger(IncomingFileTransfer.class.getName()); 060 061 private FileTransferRequest recieveRequest; 062 063 private InputStream inputStream; 064 065 protected IncomingFileTransfer(FileTransferRequest request, 066 FileTransferNegotiator transferNegotiator) { 067 super(request.getRequestor(), request.getStreamID(), transferNegotiator); 068 this.recieveRequest = request; 069 } 070 071 /** 072 * Negotiates the stream method to transfer the file over and then returns 073 * the negotiated stream. 074 * 075 * @return The negotiated InputStream from which to read the data. 076 * @throws SmackException 077 * @throws XMPPErrorException If there is an error in the negotiation process an exception 078 * is thrown. 079 */ 080 public InputStream recieveFile() throws SmackException, XMPPErrorException { 081 if (inputStream != null) { 082 throw new IllegalStateException("Transfer already negotiated!"); 083 } 084 085 try { 086 inputStream = negotiateStream(); 087 } 088 catch (XMPPErrorException e) { 089 setException(e); 090 throw e; 091 } 092 093 return inputStream; 094 } 095 096 /** 097 * This method negotitates the stream and then transfer's the file over the negotiated stream. 098 * The transfered file will be saved at the provided location. 099 * <p/> 100 * This method will return immedialtly, file transfer progress can be monitored through several 101 * methods: 102 * <p/> 103 * <UL> 104 * <LI>{@link FileTransfer#getStatus()} 105 * <LI>{@link FileTransfer#getProgress()} 106 * <LI>{@link FileTransfer#isDone()} 107 * </UL> 108 * 109 * @param file The location to save the file. 110 * @throws SmackException when the file transfer fails 111 * @throws IOException 112 * @throws IllegalArgumentException This exception is thrown when the the provided file is 113 * either null, or cannot be written to. 114 */ 115 public void recieveFile(final File file) throws SmackException, IOException { 116 if (file == null) { 117 throw new IllegalArgumentException("File cannot be null"); 118 } 119 if (!file.exists()) { 120 file.createNewFile(); 121 } 122 if (!file.canWrite()) { 123 throw new IllegalArgumentException("Cannot write to provided file"); 124 } 125 126 Thread transferThread = new Thread(new Runnable() { 127 public void run() { 128 try { 129 inputStream = negotiateStream(); 130 } 131 catch (Exception e) { 132 setStatus(FileTransfer.Status.error); 133 setException(e); 134 return; 135 } 136 137 OutputStream outputStream = null; 138 try { 139 outputStream = new FileOutputStream(file); 140 setStatus(Status.in_progress); 141 writeToStream(inputStream, outputStream); 142 } 143 catch (FileNotFoundException e) { 144 setStatus(Status.error); 145 setError(Error.bad_file); 146 setException(e); 147 } 148 catch (IOException e) { 149 setStatus(Status.error); 150 setError(Error.stream); 151 setException(e); 152 } 153 154 155 if (getStatus().equals(Status.in_progress)) { 156 setStatus(Status.complete); 157 } 158 if (inputStream != null) { 159 try { 160 inputStream.close(); 161 } catch (IOException e) { 162 LOGGER.log(Level.WARNING, "Closing input stream", e); 163 } 164 } 165 if (outputStream != null) { 166 try { 167 outputStream.close(); 168 } catch (IOException e) { 169 LOGGER.log(Level.WARNING, "Closing output stream", e); 170 } 171 } 172 } 173 }, "File Transfer " + streamID); 174 transferThread.start(); 175 } 176 177 private InputStream negotiateStream() throws SmackException, XMPPErrorException { 178 setStatus(Status.negotiating_transfer); 179 final StreamNegotiator streamNegotiator = negotiator 180 .selectStreamNegotiator(recieveRequest); 181 setStatus(Status.negotiating_stream); 182 FutureTask<InputStream> streamNegotiatorTask = new FutureTask<InputStream>( 183 new Callable<InputStream>() { 184 185 public InputStream call() throws Exception { 186 return streamNegotiator 187 .createIncomingStream(recieveRequest.getStreamInitiation()); 188 } 189 }); 190 streamNegotiatorTask.run(); 191 InputStream inputStream; 192 try { 193 inputStream = streamNegotiatorTask.get(15, TimeUnit.SECONDS); 194 } 195 catch (InterruptedException e) { 196 throw new SmackException("Interruption while executing", e); 197 } 198 catch (ExecutionException e) { 199 throw new SmackException("Error in execution", e); 200 } 201 catch (TimeoutException e) { 202 throw new SmackException("Request timed out", e); 203 } 204 finally { 205 streamNegotiatorTask.cancel(true); 206 } 207 setStatus(Status.negotiated); 208 return inputStream; 209 } 210 211 public void cancel() { 212 setStatus(Status.cancelled); 213 } 214 215}