View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  package org.xnap.plugin.jtella;
21  
22  import java.io.BufferedOutputStream;
23  import java.io.File;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.net.HttpURLConnection;
29  import java.net.URL;
30  import java.util.Hashtable;
31  
32  import javax.swing.Action;
33  
34  import org.apache.log4j.Logger;
35  import org.xnap.io.ThrottledInputStream;
36  import org.xnap.peer.Peer;
37  import org.xnap.plugin.Plugin;
38  import org.xnap.transfer.AbstractTransfer;
39  import org.xnap.transfer.Download;
40  import org.xnap.util.FileHelper;
41  import org.xnap.util.FiniteStateMachine;
42  import org.xnap.util.IllegalOperationException;
43  import org.xnap.util.State;
44  
45  import com.kenmccrary.jtella.SearchReplyMessage;
46  
47  /***
48   * Downloads a file.
49   */
50  public class JTellaDownload extends AbstractTransfer implements Download {
51  
52      //--- Constant(s) ---
53  
54      private static final Hashtable TRANSITION_TABLE;
55      static {
56  		State[][] table = new State[][] {
57  			{ State.NOT_STARTED,  
58  			  State.CONNECTING, },
59  			{ State.CONNECTING, 
60  			  State.DOWNLOADING, State.ABORTING, State.FAILED, },
61  			{ State.DOWNLOADING, 
62  			  State.SUCCEEDED, State.ABORTING, State.FAILED, },
63  			{ State.ABORTING,
64  			  State.ABORTED }
65  		};
66  
67  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
68      }
69      
70      //--- Data field(s) ---
71  
72      private static Logger logger = Logger.getLogger(JTellaDownload.class);
73  
74      private File file;
75      private JTellaSearchResult result;
76      private long bytesTransferred = 0;
77      private StateMachine sm = new StateMachine();
78      private DownloadRunner runner;
79  
80      //--- Constructor(s) ---
81      
82      public JTellaDownload(JTellaSearchResult result) 
83      {
84  		this.result = result;
85      }
86  
87      //--- Method(s) ---
88  
89      public long getBytesTransferred() 
90      {
91  		return bytesTransferred;
92      }
93  
94      public Action[] getActions()
95      {
96  		return null;
97      }
98  
99      public File getFile()
100     {
101 		return file;
102     }
103 
104     public String getFilename()
105     {
106 		return result.getFilename();
107     }
108 
109     public long getFilesize()
110     {
111 		return result.getFilesize();
112     }
113 
114     public Peer getPeer()
115     {
116 		return result.getPeer();
117     }
118 
119     public Plugin getPlugin()
120     {
121 		return JTellaPlugin.getInstance();
122     }
123 
124     public String getStatus()
125     {
126 		return sm.getDescription();
127     }
128 
129     public long getTotalBytesTransferred()
130     {
131 		return bytesTransferred;
132     }
133 
134     public boolean isDone()
135     {
136 		State s = sm.getState();
137 		return s == State.SUCCEEDED || s == State.ABORTED || s == State.FAILED;
138     }
139 
140     public boolean isFailed()
141     {
142 		State s = sm.getState();
143 		return s == State.FAILED;
144     }
145 
146     public boolean isRunning()
147     {
148 		State s = sm.getState();
149 		return s == State.CONNECTING || s == State.DOWNLOADING 
150 			|| s == State.ABORTING;
151     }
152 
153     public void start()
154     {
155 		setState(State.CONNECTING);
156     }
157 
158     private void setState(State newState, String description)
159     {
160 		sm.setState(newState, description);
161 		stateChanged();
162     }
163 
164     private void setState(State newState)
165     {
166 		sm.setState(newState);
167 		stateChanged();
168     }
169 
170     //--- Inner Class(es) ---
171 
172     private class StateMachine extends FiniteStateMachine
173     {
174 
175 		//--- Constructor(s) ---
176 
177 		public StateMachine()
178 		{
179 			super(State.NOT_STARTED, TRANSITION_TABLE);
180 		}
181 
182 		//--- Method(s) ---
183 
184 		protected synchronized void stateChanged(State oldState,
185 												 State newState)
186 		{
187 			if (newState == State.CONNECTING) {
188 				runner = new DownloadRunner();
189 				Thread t 
190 					= new Thread(runner, "JTellaDownload:"
191 								 + getFilename());
192 				t.start();
193 				return;
194 			}
195 			else if (oldState == State.CONNECTING) {
196 				if (newState == State.ABORTING) {
197 					runner.die = true;
198 					return;
199 				}
200 			}
201 		}
202 	
203     }
204 
205     private class DownloadRunner implements Runnable
206     {
207 
208 		//--- Data field(s) ---
209 
210 		private boolean die = false;
211 
212 		//--- Method(s) ---
213 
214 		public String getFilenameRequest()
215 		{
216 			StringBuffer sb = new StringBuffer();
217 			sb.append("/get/");
218 			sb.append(result.getFileRecord().getIndex());
219 			sb.append("/");
220 			sb.append(result.getFilename());
221 			sb.append("/");
222 			return sb.toString();
223 		}
224 
225 		public void run()
226 		{
227 			HttpURLConnection connection = null;
228 			InputStream in = null;
229 			try {
230 				SearchReplyMessage reply = result.getReply();
231 				URL url = new URL("http", reply.getIPAddress(),
232 								  reply.getPort(), getFilenameRequest());
233 
234 				connection = (HttpURLConnection)url.openConnection();
235 		
236 				if (die) {
237 					return;
238 				}
239 
240 				if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
241 					in = new ThrottledInputStream(connection.getInputStream());
242 					download(in);
243 				} 
244 				else {
245 					throw new IOException(connection.getResponseMessage());
246 				}
247 
248 				setState(State.SUCCEEDED);
249 			}
250 			catch (IOException e) {
251 				try {
252 					logger.warn("connection failed", e);
253 					setState(State.FAILED, e.getLocalizedMessage());
254 				}
255 				catch (IllegalOperationException e2) {
256 				}
257 			}
258 			finally {
259 				if (in != null) {
260 					try {
261 						in.close();
262 					}
263 					catch (IOException e2) {
264 					}
265 				}
266 				if (connection != null) {
267 					connection.disconnect();
268 				}
269 			}
270 		}
271 
272 		public void download(InputStream in) throws IOException
273 		{
274 			OutputStream out = null;
275 			try {
276 				file = FileHelper.createIncompleteFile(result.getFilename());
277 				out = new BufferedOutputStream(new FileOutputStream(file));
278 		
279 				setState(State.DOWNLOADING);
280 
281 				byte[] data = new byte[512];
282 				while (!die && bytesTransferred < getFilesize()) {
283 					// compute the number of bytes to read
284 					long toRead = getFilesize() - bytesTransferred;
285 					int len = (int)Math.min(toRead, data.length);
286 		    
287 					len = in.read(data, 0, len);
288 		    
289 					if (len == -1) {
290 						break;
291 					}
292 		    
293 					out.write(data, 0, len);
294 					bytesTransferred += len;
295 				} 
296 				out.flush();
297 			}
298 			finally {
299 				transferStopped();
300 
301 				if (out != null) {
302 					try {
303 						out.close();
304 					}
305 					catch (IOException e) {
306 					}
307 				}
308 			}
309 		}
310 
311     }
312 
313 }