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 }