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 java.awt.event.ActionEvent;
23  import java.io.BufferedInputStream;
24  import java.io.BufferedOutputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.net.ServerSocket;
31  import java.net.Socket;
32  import java.util.Hashtable;
33  
34  import javax.swing.Action;
35  
36  import org.apache.log4j.Logger;
37  import org.xnap.XNap;
38  import org.xnap.io.ThrottledOutputStream;
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.FiniteStateMachine;
45  import org.xnap.util.IllegalOperationException;
46  import org.xnap.util.State;
47  
48  /***
49   * Uploads a file via the dcc protocol.
50   */
51  public class DccUpload extends AbstractUpload {
52  
53      //--- Constant(s) ---
54  
55  	public static final int SOCKET_TIMEOUT = 60 * 1000;
56  
57      private static final Hashtable TRANSITION_TABLE;
58      static {
59  		State[][] table = new State[][] {
60  			{ State.NOT_STARTED,  
61  			  State.CONNECTING, },
62  			{ State.CONNECTING, 
63  			  State.UPLOADING, State.STOPPING, State.STOPPED, },
64  			{ State.UPLOADING, 
65  			  State.SUCCEEDED, State.STOPPING, State.STOPPED, },
66  			{ State.STOPPING,
67  			  State.SUCCEEDED, State.STOPPED }
68  		};
69  
70  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
71      }
72      
73      //--- Data field(s) ---
74  
75      private static Logger logger = Logger.getLogger(DccDownload.class);
76  
77      private File file;
78      private Peer peer;
79      private int port = 0;
80      private long totalBytesTransferred = 0;
81      private StateMachine sm = new StateMachine();
82  	private int localPort;
83  
84  	private AbstractTransferAction stopAction = new StopAction();
85  
86      //--- Constructor(s) ---
87      
88      /***
89       *
90       * @param peer the peer thae file is downloaded from
91       * @param file the destination file, the downloaded data is stored here
92       * @param host the host to connect to
93       * @param port the port to connect to
94       */
95      public DccUpload(Peer peer, File file)
96      {
97  		this.peer = peer;
98  		this.file = file;
99      }
100 
101     //--- Method(s) ---
102 
103     public long getBytesTransferred() 
104     {
105 		return totalBytesTransferred;
106     }
107 
108     public Action[] getActions()
109     {
110 		return new Action[] { stopAction, };
111     }
112 
113     public File getFile()
114     {
115 		return file;
116     }
117 
118     public String getFilename()
119     {
120 		return file.getName();
121     }
122 
123     public long getFilesize()
124     {
125 		return file.length();
126     }
127 
128 	public int getLocalPort()
129 	{
130 		return localPort;
131 	}
132 
133     public Peer getPeer()
134     {
135 		return peer;
136     }
137 
138     public Plugin getPlugin()
139     {
140 		return null;
141     }
142 
143     public String getStatus()
144     {
145 		return sm.getDescription();
146     }
147 
148     public long getTotalBytesTransferred()
149     {
150 		return totalBytesTransferred;
151     }
152 
153     public boolean isDone()
154     {
155 		State s = sm.getState();
156 		return s == State.SUCCEEDED || s == State.STOPPED;
157     }
158 
159     public boolean isRunning()
160     {
161 		State s = sm.getState();
162 		return s == State.CONNECTING || s == State.DOWNLOADING; 
163     }
164 
165     public void start()
166     {
167 		try {
168 			setState(State.CONNECTING);
169 		}
170 		catch(IllegalOperationException e) {
171 			logger.debug("unexpected exception", e);
172 		}
173     }
174 
175     private void setState(State newState, String description)
176     {
177 		sm.setState(newState, description);
178 		stateChanged();
179     }
180 
181     private void setState(State newState)
182     {
183 		sm.setState(newState);
184 		stateChanged();
185     }
186 
187     public void stop()
188     {
189 		try {
190 			setState(State.STOPPING);
191 		}
192 		catch(IllegalOperationException e) {
193 			logger.debug("unexpected exception", e);
194 		}
195     }
196 
197     //--- Inner Class(es) ---
198 
199     /***
200      * Starts the download offer.
201      */
202     public class StopAction extends AbstractStopAction {
203 
204 		//--- Constructor(s) ---
205     
206 		public StopAction() 
207 		{
208 		}
209 	
210 		//--- Method(s) ---
211 
212 		public void actionPerformed(ActionEvent event) 
213 		{
214 			DccUpload.this.stop();
215 		}
216     
217     }
218 
219     private class StateMachine extends FiniteStateMachine
220     {
221 
222 		private ServerSocket serverSocket;
223 		private UploadRunner runner;
224 
225 		//--- Constructor(s) ---
226 
227 		public StateMachine()
228 		{
229 			super(State.NOT_STARTED, TRANSITION_TABLE);
230 		}
231 
232 		//--- Method(s) ---
233 
234 		protected synchronized void stateChanged(State oldState,
235 												 State newState)
236 		{
237 			if (newState == State.CONNECTING) {
238 				try {
239 					serverSocket = new ServerSocket(0);
240 					serverSocket.setSoTimeout(SOCKET_TIMEOUT);
241 				}
242 				catch (IOException e) {
243 					logger.debug("Could not open listener socket", e);
244 					this.setState(State.STOPPED, XNap.tr("Could not open listener"));
245 				}
246 
247 				localPort = serverSocket.getLocalPort();
248 				if (localPort != 0) {
249 					logger.debug("opened socket on :" + localPort);
250 
251 					runner = new UploadRunner(serverSocket);
252 					Thread t = new Thread(runner, "DccUpload:" + getFilename());
253 					t.start();
254 				}
255 			}
256 			else if (newState == State.STOPPING) {
257 				runner.die = true;
258 
259 				stopAction.setEnabledLater(false);
260 			}
261 			else if (newState == State.STOPPED || newState == State.SUCCEEDED) {
262 				runner = null;
263 
264 				stopAction.setEnabledLater(false);
265 			}
266 
267 			if (oldState == State.CONNECTING) {
268 				try {
269 					if (serverSocket != null) {
270 						serverSocket.close();
271 					}
272 				}
273 				catch (IOException e) {
274 					logger.debug("Could not close listener socket", e);
275 				}
276 				serverSocket = null;
277 			}
278 		}
279 	
280     }
281 
282     private class UploadRunner implements Runnable
283     {
284 
285 		private ServerSocket serverSocket;
286 		private boolean die = false;
287 
288 		public UploadRunner(ServerSocket serverSocket)
289 		{
290 			this.serverSocket = serverSocket;
291 		}
292 
293 		public void run()
294 		{
295 			Socket socket = null;
296 			InputStream in = null;
297 			OutputStream out = null;
298 			try {
299 				socket = serverSocket.accept();
300 				socket.setSoTimeout(SOCKET_TIMEOUT);
301 
302 				in = new BufferedInputStream(socket.getInputStream());
303 				out = new ThrottledOutputStream
304 					(new BufferedOutputStream(socket.getOutputStream()));
305 
306 				upload(in, out);
307 
308 				setState(die ? State.STOPPED : State.SUCCEEDED);
309 			}
310 			catch (IOException e) {
311 				logger.warn("connection failed", e);
312 				setState(State.STOPPED, NetHelper.getErrorMessage(e));
313 			}
314 			finally {
315 				try {
316 					if (out != null) {
317 						out.close();
318 					}
319 					if (in != null) {
320 						in.close();
321 					}
322 					if (socket != null) {
323 						socket.close();
324 					}
325 				}
326 				catch (IOException e) {
327 				}
328 			}
329 		}
330 
331 		public void upload(InputStream in, OutputStream out) throws IOException
332 		{
333 			InputStream fileIn = null;
334 			try {
335 				fileIn = new BufferedInputStream
336 					(new FileInputStream(DccUpload.this.getFile()));
337 
338 				setState(State.UPLOADING);
339             
340 				byte[] inBuffer = new byte[4];
341 				byte[] outBuffer = new byte[1024];
342 
343 				while (!die && totalBytesTransferred < getFilesize()) {
344 					// compute the number of bytes to read
345 					long toRead = getFilesize() - totalBytesTransferred;
346 					int len = (int)Math.min(toRead, outBuffer.length);
347 		    
348 					len = fileIn.read(outBuffer, 0, len);
349 		    
350 					if (len == -1) {
351 						break;
352 					}
353 		    
354 					out.write(outBuffer, 0, len);
355 					totalBytesTransferred += len;
356 		    
357 					// the receiver send us how many bytes he has received so far
358 					in.read(inBuffer, 0, inBuffer.length);
359 				}
360 			}
361 			finally {
362 				transferStopped();
363 
364 				if (fileIn != null) {
365 					try {
366 						fileIn.close();
367 					}
368 					catch (IOException e) {
369 					}
370 				}
371 			}
372 		}
373 
374     }
375 
376 }