001    /*
002     * This file is part of the Echo Point Project.  This project is a
003     * collection of Components that have extended the Echo Web Application
004     * Framework Version 3.
005     *
006     * Version: MPL 1.1
007     *
008     * The contents of this file are subject to the Mozilla Public License Version
009     * 1.1 (the "License"); you may not use this file except in compliance with
010     * the License. You may obtain a copy of the License at
011     * http://www.mozilla.org/MPL/
012     *
013     * Software distributed under the License is distributed on an "AS IS" basis,
014     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015     * for the specific language governing rights and limitations under the
016     * License.
017     */
018    package echopoint.tucana;
019    
020    import java.io.Serializable;
021    import java.util.LinkedList;
022    
023    /**
024     * Contains information about the progress of a file upload. This class is
025     * thread-safe.
026     *
027     * <p><b>Note:</b> Development of this component was sponsored by <a
028     * href='http://tcnbroadcasting.com/index.jsp' target='_top'>TCN
029     * Broadcasting</a>.  We are grateful for their support and sponsorship.</p>
030     *
031     * @author Echo File Transfer Library
032     * @version $Id: UploadProgress.java 106 2009-02-03 16:00:33Z sptrakesh $
033     */
034    public class UploadProgress implements Serializable
035    {
036      private static final long serialVersionUID = 1l;
037    
038      /**
039       * The minimum number of milestones required for calculation of the tranfer
040       * rate.
041       */
042      private static final int MINIMUM_MILESTONE_COUNT = 2;
043    
044      /**
045       * The maximum number of milestones that should be kept for calculation of
046       * the tranfer rate.
047       */
048      private static final int MAXIMUM_MILESTONE_COUNT = 10;
049    
050      /** The minimum time interval in milliseconds between milestones. */
051      private static final int MILESTONE_INTERVAL = 250;
052    
053      /**
054       * The minimum number of bytes that should have been transferred between
055       * milestones.
056       */
057      private static final int MILESTONE_BYTE_INTERVAL = 10240 / 4;
058    
059      /** The total length in bytes of the content being uploaded. */
060      private final long contentLength;
061    
062      /** The total bytes that have been read so far. */
063      private long bytesRead;
064    
065      /** The list of milestones that have been completed so far. */
066      private final LinkedList<Milestone> milestones;
067    
068      /** The number of bytes read at the end of the previous milestone. */
069      private long lastMilestoneBytesRead;
070    
071      /** The status of the current content upload. */
072      private Status status = Status.inprogress;
073    
074      /**
075       * An optional message returned by the service to the client.  Usually
076       * used to indicate errors.
077       */
078      private String message = "";
079    
080      /** @param contentLength the total number of bytes, <code>-1</code> if unknown */
081      public UploadProgress( long contentLength )
082      {
083        this.contentLength = contentLength;
084        this.milestones = new LinkedList<Milestone>();
085      }
086    
087      /**
088       * Returns the number of bytes that have been read so far.
089       *
090       * @return the number of bytes read.
091       */
092      public long getBytesRead()
093      {
094        return bytesRead;
095      }
096    
097      /**
098       * Returns the total number of bytes.
099       *
100       * @return the total number of bytes, <code>-1</code> if unknown.
101       */
102      public long getContentLength()
103      {
104        return contentLength;
105      }
106    
107      /**
108       * Returns the completion percentage.
109       *
110       * @return the percentage as a float between <code>0</code> and
111       *         <code>100</code>, returns <code>-1</code> if the total number of
112       *         bytes to be read is unknown.
113       */
114      public float getPercentCompleted()
115      {
116        if ( contentLength == -1 )
117        {
118          return -1;
119        }
120        if ( contentLength == 0 )
121        {
122          return 1;
123        }
124        return (float) bytesRead / contentLength * 100f;
125      }
126    
127      /**
128       * Returns the throughput rate in bytes per second.
129       *
130       * @return the throughput rate as a long, returns <code>-1</code> if the
131       *         transfer rate is unknown yet.
132       */
133      public long getTransferRate()
134      {
135        Milestone firstMarker;
136        Milestone lastMarker;
137        synchronized ( milestones )
138        {
139          if ( milestones.size() < MINIMUM_MILESTONE_COUNT )
140          {
141            return -1;
142          }
143          firstMarker = milestones.getFirst();
144          lastMarker = milestones.getLast();
145        }
146    
147        long byteDiff = lastMarker.bytesRead - firstMarker.bytesRead;
148        long timeDiff = lastMarker.timeStamp - firstMarker.timeStamp;
149    
150        return byteDiff * 1000 / timeDiff;
151      }
152    
153      /**
154       * Returns the estimated time left to complete the upload.
155       *
156       * @return the estimated time in seconds, returns <code>-1</code> if the
157       *         estimated time is unknown.
158       */
159      public int getEstimatedTimeLeft()
160      {
161        if ( contentLength == -1 )
162        {
163          return -1;
164        }
165        if ( contentLength == bytesRead )
166        {
167          return 0;
168        }
169    
170        long transferRate = getTransferRate();
171        if ( transferRate == -1 )
172        {
173          return -1;
174        }
175        return Math.round( ( contentLength - bytesRead ) / transferRate );
176      }
177    
178      /**
179       * Sets the number of bytes that have been read so far.
180       *
181       * @param bytesRead the number of bytes read
182       */
183      public void setBytesRead( long bytesRead )
184      {
185        this.bytesRead = bytesRead;
186    
187        if ( lastMilestoneBytesRead > 0 &&
188            bytesRead - lastMilestoneBytesRead < MILESTONE_BYTE_INTERVAL )
189        {
190          // prevent slowdown of upload due to excessive amount of calls
191          return;
192        }
193    
194        synchronized ( milestones )
195        {
196          long now = System.currentTimeMillis();
197          if ( milestones.isEmpty() ||
198              now >= ( milestones.getLast().timeStamp + MILESTONE_INTERVAL ) )
199          {
200            milestones.add( new Milestone( bytesRead, now ) );
201            lastMilestoneBytesRead = bytesRead;
202            if ( milestones.size() > MAXIMUM_MILESTONE_COUNT )
203            {
204              milestones.removeFirst();
205            }
206          }
207        }
208      }
209    
210      /**
211       * Return the status of the current file upload process.
212       *
213       * @return The status value.
214       */
215      public Status getStatus()
216      {
217        return status;
218      }
219    
220      /**
221       * Set the status of the current file upload process.
222       *
223       * @param status The status value to set.
224       */
225      public void setStatus( final Status status )
226      {
227        this.status = status;
228      }
229    
230      /**
231       * Accessor for property 'message'.
232       *
233       * @return Value for property 'message'.
234       */
235      public String getMessage()
236      {
237        return message;
238      }
239    
240      /**
241       * Mutator for property 'message'.
242       *
243       * @param message Value to set for property 'message'.
244       */
245      public void setMessage( final String message )
246      {
247        this.message = message;
248      }
249    
250      private static final class Milestone implements Serializable
251      {
252        private static final long serialVersionUID = 1l;
253    
254        private final long bytesRead;
255    
256        private final long timeStamp;
257    
258        private Milestone( long bytesRead, long timeStamp )
259        {
260          this.bytesRead = bytesRead;
261          this.timeStamp = timeStamp;
262        }
263      }
264    }