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