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.transfer;
21  
22  import javax.swing.Action;
23  
24  import java.awt.event.ActionEvent;
25  import java.io.BufferedInputStream;
26  import java.io.BufferedOutputStream;
27  import java.io.File;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.net.Socket;
33  import java.util.Hashtable;
34  
35  import org.apache.log4j.Logger;
36  import org.xnap.XNap;
37  import org.xnap.gui.util.IconHelper;
38  import org.xnap.io.ThrottledInputStream;
39  import org.xnap.net.NetHelper;
40  import org.xnap.peer.Peer;
41  import org.xnap.plugin.Plugin;
42  import org.xnap.transfer.action.AbstractStopAction;
43  import org.xnap.transfer.action.AbstractTransferAction;
44  import org.xnap.util.FileHelper;
45  import org.xnap.util.FiniteStateMachine;
46  import org.xnap.util.IllegalOperationException;
47  import org.xnap.util.State;
48  
49  /***
50   * Downloads a file via the dcc protocol.
51   */
52  public class DccDownload extends AbstractTransfer implements Download {
53  
54      //--- Constant(s) ---
55  
56      private static final Hashtable TRANSITION_TABLE;
57      static {
58  		State[][] table = new State[][] {
59  			{ State.NOT_STARTED,  
60  			  State.CONNECTING, State.STOPPED },
61  			{ State.CONNECTING, 
62  			  State.DOWNLOADING, State.STOPPING, State.STOPPED, },
63  			{ State.DOWNLOADING, 
64  			  State.SUCCEEDED, State.STOPPING, State.STOPPED, },
65  			{ State.STOPPING,
66  			  State.SUCCEEDED, State.STOPPED }
67  		};
68  
69  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
70      }
71      
72      //--- Data field(s) ---
73  
74      private static Logger logger = Logger.getLogger(DccDownload.class);
75  
76      private File file;
77      private String filename;
78      private long filesize;
79      private String host;
80      private Peer peer;
81      private int port;
82      private long totalBytesTransferred = 0;
83      private StateMachine sm = new StateMachine();
84  
85  	private AbstractTransferAction acceptAction = new AcceptAction();
86  	private AbstractTransferAction rejectAction = new RejectAction();
87  	private AbstractTransferAction stopAction = new StopAction();
88  
89      //--- Constructor(s) ---
90      
91      /***
92       *
93       * @param peer the peer thae file is downloaded from
94       * @param file the destination file, the downloaded data is stored here
95       * @param host the host to connect to
96       * @param port the port to connect to
97       */
98      public DccDownload(Peer peer, String filename, long filesize, 
99  					   String host, int port) 
100     {
101 		this.peer = peer;
102 		this.filename = filename;
103 		this.filesize = filesize;
104 		this.host = host;
105 		this.port = port;
106 
107 		stopAction.setEnabledLater(false);
108     }
109 
110     //--- Method(s) ---
111 
112     public long getBytesTransferred() 
113     {
114 		return totalBytesTransferred;
115     }
116 
117     public Action[] getActions()
118     {
119 		return new Action[] { acceptAction, rejectAction, stopAction, };
120     }
121 
122     public File getFile()
123     {
124 		return file;
125     }
126 
127     public String getFilename()
128     {
129 		return filename;
130     }
131 
132     public long getFilesize()
133     {
134 		return filesize;
135     }
136 
137     public Peer getPeer()
138     {
139 		return peer;
140     }
141 
142     public Plugin getPlugin()
143     {
144 		return null;
145     }
146 
147     public String getStatus()
148     {
149 		return sm.getDescription();
150     }
151 
152     public long getTotalBytesTransferred()
153     {
154 		return totalBytesTransferred;
155     }
156 
157     public boolean isDone()
158     {
159 		State s = sm.getState();
160 		return s == State.SUCCEEDED || s == State.STOPPED;
161     }
162 
163     public boolean isRunning()
164     {
165 		State s = sm.getState();
166 		return s == State.NOT_STARTED || s == State.CONNECTING
167 			|| s == State.DOWNLOADING; 
168     }
169 
170     public void reject()
171     {
172 		try {
173 			setState(State.STOPPED, XNap.tr("Download rejected"));
174 		}
175 		catch(IllegalOperationException e) {
176 			logger.debug("unexpected exception", e);
177 		}
178     }	
179 
180     public void start()
181     {
182 		try {
183 			setState(State.CONNECTING);
184 		}
185 		catch(IllegalOperationException e) {
186 			logger.debug("unexpected exception", e);
187 		}
188     }
189 
190     private void setState(State newState, String description)
191     {
192 		sm.setState(newState, description);
193 		stateChanged();
194     }
195 
196     private void setState(State newState)
197     {
198 		sm.setState(newState);
199 		stateChanged();
200     }
201 
202     public void stop()
203     {
204 		try {
205 			setState(State.STOPPING);
206 		}
207 		catch(IllegalOperationException e) {
208 			logger.debug("unexpected exception", e);
209 		}
210     }
211 
212     //--- Inner Class(es) ---
213 
214     /***
215      * Starts the download offer.
216      */
217     public class AcceptAction extends AbstractTransferAction {
218 
219 		//--- Constructor(s) ---
220     
221 		public AcceptAction() 
222 		{
223 			putValue(Action.NAME, XNap.tr("Accept"));
224 			putValue(Action.SHORT_DESCRIPTION,
225 					 XNap.tr("Download selected file(s)."));
226 			putValue(IconHelper.XNAP_ICON, "down.png");
227 		}
228 	
229 		//--- Method(s) ---
230 
231 		public boolean equals(Object o)
232 		{
233 			return o instanceof AcceptAction;
234 		}
235 	
236 		public void actionPerformed(ActionEvent event) 
237 		{
238 			DccDownload.this.start();
239 		}
240     
241     }
242 
243 
244     /***
245      * Rejects the download offer.
246      */
247     public class RejectAction extends AbstractTransferAction {
248 
249 		//--- Constructor(s) ---
250     
251 		public RejectAction() 
252 		{
253 			putValue(Action.NAME, XNap.tr("Reject"));
254 			putValue(Action.SHORT_DESCRIPTION,
255 					 XNap.tr("Rejects selected downloads."));
256 			putValue(IconHelper.XNAP_ICON, "stop_hand.png");
257 		}
258 		
259 		//--- Method(s) ---
260 
261 		public boolean equals(Object o)
262 		{
263 			return o instanceof RejectAction;
264 		}
265 	
266 		public void actionPerformed(ActionEvent event) 
267 		{
268 			DccDownload.this.reject();
269 		}
270     
271     }
272 
273     /***
274      * Starts the download offer.
275      */
276     public class StopAction extends AbstractStopAction {
277 
278 		//--- Constructor(s) ---
279     
280 		public StopAction() 
281 		{
282 		}
283 	
284 		//--- Method(s) ---
285 
286 		public void actionPerformed(ActionEvent event) 
287 		{
288 			DccDownload.this.stop();
289 		}
290     
291     }
292 
293     private class StateMachine extends FiniteStateMachine
294     {
295 
296 		private DownloadRunner runner;
297 
298 		//--- Constructor(s) ---
299 
300 		public StateMachine()
301 		{
302 			super(State.NOT_STARTED, TRANSITION_TABLE);
303 
304 			setDescription(XNap.tr("Please accept or reject download"));
305 		}
306 
307 		//--- Method(s) ---
308 
309 		protected synchronized void stateChanged(State oldState,
310 												 State newState)
311 		{
312 			if (newState == State.CONNECTING) {
313 				runner = new DownloadRunner();
314 				Thread t = new Thread(runner, "DccDownload:" + getFilename());
315 				t.start();
316 
317 				stopAction.setEnabledLater(true);
318 			}
319 			else if (newState == State.STOPPING) {
320 				runner.die = true;
321 				
322 				stopAction.setEnabledLater(false);
323 			}
324 			else if (newState == State.STOPPED || newState == State.SUCCEEDED) {
325 				runner = null;
326 
327 				stopAction.setEnabledLater(false);
328 			}
329 
330 			if (oldState == State.NOT_STARTED) {
331 				acceptAction.setEnabledLater(false);
332 				rejectAction.setEnabledLater(false);
333 			}
334 		}
335 	
336     }
337 
338     private class DownloadRunner implements Runnable
339     {
340 
341 		//--- Data field(s) ---
342 
343 		private boolean die = false;
344 
345 		//--- Method(s) ---
346 
347 		public void run()
348 		{
349 			InputStream in = null;
350 			OutputStream out = null;
351 			try {
352 				// Connect the socket and set a timeout.
353 				logger.debug("connecting to " + host + ":" + port);
354 				Socket socket = new Socket(host, port);
355 				socket.setSoTimeout(SOCKET_TIMEOUT);
356             
357 				in = new ThrottledInputStream
358 					(new BufferedInputStream(socket.getInputStream()));
359 				out = new BufferedOutputStream(socket.getOutputStream());
360 
361 				download(in, out);
362 
363 				setState(die ? State.STOPPED : State.SUCCEEDED);
364 			}
365 			catch (IOException e) {
366 				logger.warn("connection failed", e);
367 				setState(State.STOPPED, NetHelper.getErrorMessage(e));
368 			}
369 			finally {
370 				try {
371 					if (out != null) {
372 						out.close();
373 					}
374 					if (in != null) {
375 						in.close();
376 					}
377 				}
378 				catch (IOException e) {
379 				}
380 			}
381 		}
382 
383 		public void download(InputStream in, OutputStream out) throws IOException
384 		{
385 			OutputStream fileOut = null;
386 			try {
387 				file = FileHelper.createIncompleteFile(getFilename());
388 				fileOut = new BufferedOutputStream(new FileOutputStream(file));
389 
390 				setState(State.DOWNLOADING);
391             
392 				byte[] inBuffer = new byte[1024];
393 				byte[] outBuffer = new byte[4];
394 
395 				while (!die && totalBytesTransferred < getFilesize()) {
396 					// compute the number of bytes to read
397 					long toRead = getFilesize() - totalBytesTransferred;
398 					int len = (int)Math.min(toRead, inBuffer.length);
399 		    
400 					len = in.read(inBuffer, 0, len);
401 		    
402 					if (len == -1) {
403 						break;
404 					}
405 		    
406 					fileOut.write(inBuffer, 0, len);
407 					totalBytesTransferred += len;
408 		    
409 					// send back an acknowledgement of how many bytes 
410 					// we have got so far
411 					outBuffer[0] = (byte) ((totalBytesTransferred >> 24) & 0xff);
412 					outBuffer[1] = (byte) ((totalBytesTransferred >> 16) & 0xff);
413 					outBuffer[2] = (byte) ((totalBytesTransferred >> 8) & 0xff);
414 					outBuffer[3] = (byte) ((totalBytesTransferred >> 0) & 0xff);
415 					out.write(outBuffer);
416 					out.flush();
417 				}
418 				fileOut.flush();
419 			}
420 			finally {
421 				transferStopped();
422 
423 				if (fileOut != null) {
424 					try {
425 						fileOut.close();
426 					}
427 					catch (IOException e) {
428 					}
429 				}
430 			}
431 		}
432 
433     }
434 
435 }