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}