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.IOException;
020import java.io.InputStream;
021import java.io.OutputStream;
022
023import org.jxmpp.jid.Jid;
024
025/**
026 * Contains the generic file information and progress related to a particular
027 * file transfer.
028 *
029 * @author Alexander Wenckus
030 *
031 */
032public abstract class FileTransfer {
033
034    private String fileName;
035
036    private String filePath;
037
038    private long fileSize;
039
040    private Jid peer;
041
042    private Status status = Status.initial;
043
044    private final Object statusMonitor = new Object();
045
046    protected FileTransferNegotiator negotiator;
047
048    protected String streamID;
049
050    protected long amountWritten = -1;
051
052    private Error error;
053
054    private Exception exception;
055
056    /**
057     * Buffer size between input and output
058     */
059    private static final int BUFFER_SIZE = 8192;
060
061    protected FileTransfer(Jid peer, String streamID,
062            FileTransferNegotiator negotiator) {
063        this.peer = peer;
064        this.streamID = streamID;
065        this.negotiator = negotiator;
066    }
067
068    protected void setFileInfo(String fileName, long fileSize) {
069        this.fileName = fileName;
070        this.fileSize = fileSize;
071    }
072
073    protected void setFileInfo(String path, String fileName, long fileSize) {
074        this.filePath = path;
075        this.fileName = fileName;
076        this.fileSize = fileSize;
077    }
078
079    /**
080     * Returns the size of the file being transferred.
081     *
082     * @return Returns the size of the file being transferred.
083     */
084    public long getFileSize() {
085        return fileSize;
086    }
087
088    /**
089     * Returns the name of the file being transferred.
090     *
091     * @return Returns the name of the file being transferred.
092     */
093    public String getFileName() {
094        return fileName;
095    }
096
097    /**
098     * Returns the local path of the file.
099     *
100     * @return Returns the local path of the file.
101     */
102    public String getFilePath() {
103        return filePath;
104    }
105
106    /**
107     * Returns the JID of the peer for this file transfer.
108     *
109     * @return Returns the JID of the peer for this file transfer.
110     */
111    public Jid getPeer() {
112        return peer;
113    }
114
115    /**
116     * Returns the progress of the file transfer as a number between 0 and 1.
117     *
118     * @return Returns the progress of the file transfer as a number between 0
119     *         and 1.
120     */
121    public double getProgress() {
122        if (amountWritten <= 0 || fileSize <= 0) {
123            return 0;
124        }
125        return (double) amountWritten / (double) fileSize;
126    }
127
128    /**
129     * Returns true if the transfer has been cancelled, if it has stopped because
130     * of a an error, or the transfer completed successfully.
131     *
132     * @return Returns true if the transfer has been cancelled, if it has stopped
133     *         because of a an error, or the transfer completed successfully.
134     */
135    public boolean isDone() {
136        return status == Status.cancelled || status == Status.error
137                || status == Status.complete || status == Status.refused;
138    }
139
140    /**
141     * Returns the current status of the file transfer.
142     *
143     * @return Returns the current status of the file transfer.
144     */
145    public Status getStatus() {
146        return status;
147    }
148
149    protected void setError(Error type) {
150        this.error = type;
151    }
152
153    /**
154     * When {@link #getStatus()} returns that there was an {@link Status#error}
155     * during the transfer, the type of error can be retrieved through this
156     * method.
157     *
158     * @return Returns the type of error that occurred if one has occurred.
159     */
160    public Error getError() {
161        return error;
162    }
163
164    /**
165     * If an exception occurs asynchronously it will be stored for later
166     * retrieval. If there is an error there maybe an exception set.
167     *
168     * @return The exception that occurred or null if there was no exception.
169     * @see #getError()
170     */
171    public Exception getException() {
172        return exception;
173    }
174
175    public String getStreamID() {
176        return streamID;
177    }
178
179    /**
180     * Cancels the file transfer.
181     */
182    public abstract void cancel();
183
184    protected void setException(Exception exception) {
185        this.exception = exception;
186        Status currentStatus = getStatus();
187        if (currentStatus != Status.error) {
188            updateStatus(currentStatus, Status.error);
189        }
190    }
191
192    protected void setStatus(Status status) {
193        synchronized (statusMonitor) {
194         // CHECKSTYLE:OFF
195            this.status = status;
196        }
197        // CHECKSTYLE:ON
198    }
199
200    protected boolean updateStatus(Status oldStatus, Status newStatus) {
201        synchronized (statusMonitor) {
202            if (oldStatus != status) {
203                return false;
204            }
205            status = newStatus;
206            return true;
207        }
208    }
209
210    protected void writeToStream(final InputStream in, final OutputStream out)
211                    throws IOException {
212        final byte[] b = new byte[BUFFER_SIZE];
213        int count = 0;
214        amountWritten = 0;
215
216        while ((count = in.read(b)) > 0 && !getStatus().equals(Status.cancelled)) {
217            out.write(b, 0, count);
218            amountWritten += count;
219        }
220
221        // When the amount of data written does not equal the expected amount, and
222        // the transfer was not explicitly cancelled, register an error (unless another
223        // error has already been logged).
224        if (!getStatus().equals(Status.cancelled) && getError() == null
225                && amountWritten != fileSize) {
226            setStatus(Status.error);
227            this.error = Error.connection;
228        }
229    }
230
231    /**
232     * A class to represent the current status of the file transfer.
233     *
234     * @author Alexander Wenckus
235     *
236     */
237    public enum Status {
238
239        /**
240         * An error occurred during the transfer.
241         *
242         * @see FileTransfer#getError()
243         */
244        error("Error"),
245
246        /**
247         * The initial status of the file transfer.
248         */
249        initial("Initial"),
250
251        /**
252         * The file transfer is being negotiated with the peer. The party
253         * Receiving the file has the option to accept or refuse a file transfer
254         * request. If they accept, then the process of stream negotiation will
255         * begin. If they refuse the file will not be transferred.
256         *
257         * @see #negotiating_stream
258         */
259        negotiating_transfer("Negotiating Transfer"),
260
261        /**
262         * The peer has refused the file transfer request halting the file
263         * transfer negotiation process.
264         */
265        refused("Refused"),
266
267        /**
268         * The stream to transfer the file is being negotiated over the chosen
269         * stream type. After the stream negotiating process is complete the
270         * status becomes negotiated.
271         *
272         * @see #negotiated
273         */
274        negotiating_stream("Negotiating Stream"),
275
276        /**
277         * After the stream negotiation has completed the intermediate state
278         * between the time when the negotiation is finished and the actual
279         * transfer begins.
280         */
281        negotiated("Negotiated"),
282
283        /**
284         * The transfer is in progress.
285         *
286         * @see FileTransfer#getProgress()
287         */
288        in_progress("In Progress"),
289
290        /**
291         * The transfer has completed successfully.
292         */
293        complete("Complete"),
294
295        /**
296         * The file transfer was cancelled.
297         */
298        cancelled("Cancelled");
299
300        private final String status;
301
302        Status(String status) {
303            this.status = status;
304        }
305
306        @Override
307        public String toString() {
308            return status;
309        }
310    }
311
312    /**
313     * Return the length of bytes written out to the stream.
314     * @return the amount in bytes written out.
315     */
316    public long getAmountWritten() {
317        return amountWritten;
318    }
319
320    @SuppressWarnings("JavaLangClash")
321    public enum Error {
322        /**
323         * The peer did not find any of the provided stream mechanisms
324         * acceptable.
325         */
326        not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."),
327
328        /**
329         * The provided file to transfer does not exist or could not be read.
330         */
331        bad_file("The provided file to transfer does not exist or could not be read."),
332
333        /**
334         * The remote user did not respond or the connection timed out.
335         */
336        no_response("The remote user did not respond or the connection timed out."),
337
338        /**
339         * An error occurred over the socket connected to send the file.
340         */
341        connection("An error occurred over the socket connected to send the file."),
342
343        /**
344         * An error occurred while sending or receiving the file.
345         */
346        stream("An error occurred while sending or receiving the file.");
347
348        private final String msg;
349
350        Error(String msg) {
351            this.msg = msg;
352        }
353
354        /**
355         * Returns a String representation of this error.
356         *
357         * @return Returns a String representation of this error.
358         */
359        public String getMessage() {
360            return msg;
361        }
362
363        @Override
364        public String toString() {
365            return msg;
366        }
367    }
368
369}