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}