1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2006 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20package org.jivesoftware.smackx.filetransfer;
21
22import org.jivesoftware.smack.XMPPException;
23
24import java.io.*;
25import java.util.concurrent.*;
26
27/**
28 * An incoming file transfer is created when the
29 * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)}
30 * method is invoked. It is a file being sent to the local user from another
31 * user on the jabber network. There are two stages of the file transfer to be
32 * concerned with and they can be handled in different ways depending upon the
33 * method that is invoked on this class.
34 * <p/>
35 * The first way that a file is recieved is by calling the
36 * {@link #recieveFile()} method. This method, negotiates the appropriate stream
37 * method and then returns the <b><i>InputStream</b></i> to read the file
38 * data from.
39 * <p/>
40 * The second way that a file can be recieved through this class is by invoking
41 * the {@link #recieveFile(File)} method. This method returns immediatly and
42 * takes as its parameter a file on the local file system where the file
43 * recieved from the transfer will be put.
44 *
45 * @author Alexander Wenckus
46 */
47public class IncomingFileTransfer extends FileTransfer {
48
49    private FileTransferRequest recieveRequest;
50
51    private InputStream inputStream;
52
53    protected IncomingFileTransfer(FileTransferRequest request,
54            FileTransferNegotiator transferNegotiator) {
55        super(request.getRequestor(), request.getStreamID(), transferNegotiator);
56        this.recieveRequest = request;
57    }
58
59    /**
60     * Negotiates the stream method to transfer the file over and then returns
61     * the negotiated stream.
62     *
63     * @return The negotiated InputStream from which to read the data.
64     * @throws XMPPException If there is an error in the negotiation process an exception
65     *                       is thrown.
66     */
67    public InputStream recieveFile() throws XMPPException {
68        if (inputStream != null) {
69            throw new IllegalStateException("Transfer already negotiated!");
70        }
71
72        try {
73            inputStream = negotiateStream();
74        }
75        catch (XMPPException e) {
76            setException(e);
77            throw e;
78        }
79
80        return inputStream;
81    }
82
83    /**
84     * This method negotitates the stream and then transfer's the file over the
85     * negotiated stream. The transfered file will be saved at the provided
86     * location.
87     * <p/>
88     * This method will return immedialtly, file transfer progress can be
89     * monitored through several methods:
90     * <p/>
91     * <UL>
92     * <LI>{@link FileTransfer#getStatus()}
93     * <LI>{@link FileTransfer#getProgress()}
94     * <LI>{@link FileTransfer#isDone()}
95     * </UL>
96     *
97     * @param file The location to save the file.
98     * @throws XMPPException            when the file transfer fails
99     * @throws IllegalArgumentException This exception is thrown when the the provided file is
100     *                                  either null, or cannot be written to.
101     */
102    public void recieveFile(final File file) throws XMPPException {
103        if (file != null) {
104            if (!file.exists()) {
105                try {
106                    file.createNewFile();
107                }
108                catch (IOException e) {
109                    throw new XMPPException(
110                            "Could not create file to write too", e);
111                }
112            }
113            if (!file.canWrite()) {
114                throw new IllegalArgumentException("Cannot write to provided file");
115            }
116        }
117        else {
118            throw new IllegalArgumentException("File cannot be null");
119        }
120
121        Thread transferThread = new Thread(new Runnable() {
122            public void run() {
123                try {
124                    inputStream = negotiateStream();
125                }
126                catch (XMPPException e) {
127                    handleXMPPException(e);
128                    return;
129                }
130
131                OutputStream outputStream = null;
132                try {
133                    outputStream = new FileOutputStream(file);
134                    setStatus(Status.in_progress);
135                    writeToStream(inputStream, outputStream);
136                }
137                catch (XMPPException e) {
138                    setStatus(Status.error);
139                    setError(Error.stream);
140                    setException(e);
141                }
142                catch (FileNotFoundException e) {
143                    setStatus(Status.error);
144                    setError(Error.bad_file);
145                    setException(e);
146                }
147
148                if (getStatus().equals(Status.in_progress)) {
149                    setStatus(Status.complete);
150                }
151                if (inputStream != null) {
152                    try {
153                        inputStream.close();
154                    }
155                    catch (Throwable io) {
156                        /* Ignore */
157                    }
158                }
159                if (outputStream != null) {
160                    try {
161                        outputStream.close();
162                    }
163                    catch (Throwable io) {
164                        /* Ignore */
165                    }
166                }
167            }
168        }, "File Transfer " + streamID);
169        transferThread.start();
170    }
171
172    private void handleXMPPException(XMPPException e) {
173        setStatus(FileTransfer.Status.error);
174        setException(e);
175    }
176
177    private InputStream negotiateStream() throws XMPPException {
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 XMPPException("Interruption while executing", e);
197        }
198        catch (ExecutionException e) {
199            throw new XMPPException("Error in execution", e);
200        }
201        catch (TimeoutException e) {
202            throw new XMPPException("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}
216