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  
21  package org.xnap.plugin.opennap.net;
22  
23  import javax.swing.Action;
24  import javax.swing.Icon;
25  
26  import java.awt.event.ActionEvent;
27  import java.io.BufferedInputStream;
28  import java.io.BufferedReader;
29  import java.io.File;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.OutputStream;
34  import java.net.Socket;
35  import java.util.Hashtable;
36  
37  import org.apache.log4j.Logger;
38  import org.xnap.XNap;
39  import org.xnap.net.NetHelper;
40  import org.xnap.peer.Peer;
41  import org.xnap.plugin.Plugin;
42  import org.xnap.plugin.opennap.OpenNapPlugin;
43  import org.xnap.plugin.opennap.net.msg.ExceptionListener;
44  import org.xnap.plugin.opennap.net.msg.MessageHandler;
45  import org.xnap.plugin.opennap.net.msg.MessageListener;
46  import org.xnap.plugin.opennap.net.msg.client.DirectBrowseRequestMessage;
47  import org.xnap.plugin.opennap.net.msg.server.DirectBrowseAckMessage;
48  import org.xnap.plugin.opennap.net.msg.server.DirectBrowseErrorMessage;
49  import org.xnap.plugin.opennap.net.msg.server.ServerMessage;
50  import org.xnap.plugin.opennap.user.OpenNapUser;
51  import org.xnap.search.SearchFilter;
52  import org.xnap.search.SearchHandler;
53  import org.xnap.search.SearchResult;
54  import org.xnap.transfer.AbstractDownload;
55  import org.xnap.transfer.DefaultSegment;
56  import org.xnap.transfer.DownloadManager;
57  import org.xnap.transfer.Segment;
58  import org.xnap.transfer.action.AbstractStopAction;
59  import org.xnap.util.FiniteStateMachine;
60  import org.xnap.util.QuotedStringTokenizer;
61  import org.xnap.util.Scheduler;
62  import org.xnap.util.State;
63  import org.xnap.util.XNapTask;
64  
65  public class OpenNapDirectBrowse extends AbstractDownload 
66  	implements ExceptionListener, MessageListener, 
67  			   OpenNapBrowseInterface, SocketListener {
68      
69      //--- Constant(s) ---
70      
71      public static final int REQUEST_TIMEOUT = 2 * 60 * 1000;
72  
73  	public static final int SOCKET_TIMEOUT = 1 * 60 * 1000;
74  
75  	/***
76  	 * The state transition table.
77  	 */
78      private static final Hashtable TRANSITION_TABLE;
79      static {
80  		State[][] table = new State[][] {
81  			{ State.NOT_STARTED,  
82  			  State.REQUESTING, },
83  			{ State.REQUESTING,
84  			  State.CONNECTING, State.FINISHED, State.STOPPING,  },
85  			{ State.CONNECTING, 
86  			  State.DOWNLOADING, State.FINISHED, State.STOPPING, },
87  			{ State.DOWNLOADING, 
88  			  State.FINISHED, State.STOPPING, },
89  			{ State.STOPPING,
90  			  State.FINISHED }
91  		};
92  		
93  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
94      }
95  
96      //--- Data field(s) ---
97  
98      protected static Logger logger = Logger.getLogger(OpenNapDirectBrowse.class);
99  
100 	private StateMachine sm = new StateMachine();
101 	
102 	private OpenNapUser user;
103 
104     private SearchHandler handler;
105 
106 	private BrowseDownloadSocket inSocket;
107 
108 	private long bytesTransferred;
109 	private DefaultSegment segment;
110 	private long size;
111 
112 	private boolean failed;
113 
114     //--- Constructor(s) ---
115 
116     public OpenNapDirectBrowse(OpenNapUser user)
117     {
118 		this.user = user;
119     }
120 
121     //--- Method(s) ---
122 
123     public void exceptionThrown(Exception e)
124     {
125 		failed = true;
126 		setState(State.FINISHED, e.getLocalizedMessage());
127     }
128 
129 	public File getFile()
130 	{
131 		return null;
132 	}
133 
134 	public String getFilename()
135 	{
136 		return XNap.tr("OpenNap Direct Browse");
137 	}
138 
139     /***
140      * 
141      */
142     public long getFilesize() 
143 	{
144         return size;
145     }
146 
147     /***
148      * Returns <code>null</code>.
149      */
150     public SearchFilter getFilter()
151     {
152 		return null;
153     }
154 
155     /***
156      * Returns the name of the user that is browsed.
157      */
158     public String getName()
159     {
160 		return user.getName();
161     }
162 
163     public Plugin getPlugin()
164     {
165 		return OpenNapPlugin.getInstance();
166     }
167 
168 	/***
169 	 * @see xnap.transfer.Transfer#getActions()
170 	 */
171 	public Action[] getActions() 
172 	{
173 		return new Action[] { new StopAction() };
174 	}
175 
176 	/***
177 	 * 
178 	 */
179 	public long getBytesTransferred() 
180 	{
181 		return bytesTransferred;
182 	}
183 
184     public Icon getIcon()
185     {
186 		return OpenNapPlugin.ICON_16;
187     }
188 
189 	/***
190 	 * @see xnap.transfer.Transfer#getPeer()
191 	 */
192 	public Peer getPeer() 
193 	{
194 		return user;
195 	}
196 
197 	public Segment[] getSegments()
198 	{
199 		return (segment != null) ? new Segment[] { segment } : null;
200 	}
201 
202 	/***
203 	 * @see xnap.transfer.Transfer#getStatus()
204 	 */
205 	public String getStatus() 
206 	{
207 		return sm.getDescription();
208 	}
209 
210 	/***
211 	 * @see xnap.transfer.Transfer#getTotalBytesTransferred()
212 	 */
213 	public long getTotalBytesTransferred() 
214 	{
215 		return (isDone()) ? bytesTransferred : 0;
216 	}
217 
218     public boolean isDone()
219     {
220 		return sm.getState() == State.FINISHED;
221     }
222 
223 	public boolean isFailed()
224 	{
225 		return failed;
226 	}
227 
228     public boolean isRunning()
229     {
230 		return sm.getState() == State.DOWNLOADING;
231     }
232 
233     /***
234      * Handles messages received from the server as response to the download
235      * request. Notifies the parent download container if download is ready to
236      * start or if it should be removed due to an error message received from
237      * the server.
238      */
239     public void messageReceived(ServerMessage msg)
240     {
241 		if (msg instanceof DirectBrowseErrorMessage) {
242 		    DirectBrowseErrorMessage m = (DirectBrowseErrorMessage)msg;
243 		    if (m.nick.equals(user.getName())) {
244 				msg.consume();
245 				setState(State.FINISHED, m.message);
246 		    }
247 		}
248 		else if (msg instanceof DirectBrowseAckMessage) {
249 		    DirectBrowseAckMessage m = (DirectBrowseAckMessage)msg;
250 		    if (m.nick.equals(user.getName())) {
251 				msg.consume();
252 				user.setHost(m.ip);
253 				user.setPort(m.port);
254 				setState(State.CONNECTING);
255 		    }
256 		}
257     }
258 
259     /***
260      * Returns <code>true</code>.
261      */
262     public boolean showTree()
263     {
264 		return true;
265     }
266 
267 	public boolean socketReceived(IncomingSocket s)
268 	{
269 		if (s instanceof BrowseDownloadSocket) {
270 			// there is no way to verify if this socket is really ours
271 			// so just take it
272 			BrowseDownloadSocket b = (BrowseDownloadSocket)s;
273 			if (user.getName().equals(b.nick)) {
274 				inSocket = b;
275 				setState(State.CONNECTING);
276 				return true;
277 			}
278 	    }
279 		return false;
280 	}
281 
282     /***
283      * Starts this search.
284      *
285      * @param handler the object that handles the results
286      */
287     public void start(SearchHandler handler)
288     {
289 		this.handler = handler;
290 
291 		setState(State.REQUESTING);
292     }
293 
294     /***
295      * Cancels this search.
296      */
297     public void stop()
298     {
299 		setState(State.STOPPING);
300     }
301 
302     void setState(State newState, String description)
303     {
304 		sm.setState(newState, description);
305 		stateChanged();
306 		handler.stateChanged(this);
307     }
308 
309     void setState(State newState)
310     {
311 		sm.setState(newState);
312 		stateChanged();
313 		handler.stateChanged(this);
314     }
315 
316     //--- Inner Class(es) ---
317 
318     private class StateMachine extends FiniteStateMachine {
319 
320 		//--- Data Field(s) ---
321 
322 		private OpenNapDirectBrowseDownloadRunner runner;
323 		private Thread t;
324 		private XNapTask watcher;
325 
326 		//--- Constructor(s) ---
327 
328 		public StateMachine()
329 		{
330 			super(State.NOT_STARTED, TRANSITION_TABLE);
331 		}
332 
333 		//--- Method(s) ---
334 
335 		protected synchronized void stateChanged(State oldState,
336 												 State newState)
337 		{
338 			if (newState == State.REQUESTING) {
339 				DownloadManager.getInstance().add(OpenNapDirectBrowse.this);
340 
341 				user.getServer().getListener().addSocketListener
342 					(OpenNapDirectBrowse.this);
343 
344 				MessageHandler.subscribe(DirectBrowseAckMessage.TYPE, 
345 										 OpenNapDirectBrowse.this);
346 				MessageHandler.subscribe(DirectBrowseErrorMessage.TYPE,
347 										 OpenNapDirectBrowse.this);
348 
349 				DirectBrowseRequestMessage msg
350 					= new DirectBrowseRequestMessage(user.getName());
351 				msg.setExceptionListener(OpenNapDirectBrowse.this);
352 				MessageHandler.send(user.getServer(), msg);
353 
354 				watcher = new WatchTask();
355 				Scheduler.run(REQUEST_TIMEOUT, watcher);
356 			}
357 			else if (newState == State.CONNECTING) {
358 				runner = new OpenNapDirectBrowseDownloadRunner(inSocket);
359 				t = new Thread(runner, "OpenNapDirectBrowseDownload:" 
360 							   + user.getName());
361 				t.start();
362 			}
363 			else if (newState == State.DOWNLOADING) {
364 				transferStarted();
365 			}
366 			else if (newState == State.STOPPING) {
367 				if (oldState == State.CONNECTING
368 					|| oldState == State.DOWNLOADING) {
369 					runner.stop();
370 					t.interrupt();
371 				}
372 				else {
373 					this.setState(State.FINISHED);
374 				}
375 			}
376 			else if (newState == State.FINISHED) {
377 				DownloadManager.getInstance().remove(OpenNapDirectBrowse.this);
378 			}
379 
380 
381 			if (oldState == State.REQUESTING) {
382 				if (watcher != null) {
383 					watcher.cancel();
384 					watcher = null;
385 				}
386 
387 				user.getServer().getListener().removeSocketListener
388 					(OpenNapDirectBrowse.this);
389 
390 				MessageHandler.unsubscribe(DirectBrowseAckMessage.TYPE, 
391 										 OpenNapDirectBrowse.this);
392 				MessageHandler.unsubscribe(DirectBrowseErrorMessage.TYPE,
393 										 OpenNapDirectBrowse.this);
394 			}
395 			else if (oldState == State.DOWNLOADING) {
396 				transferStopped();
397 			}
398 		}
399 	}
400 
401 	private class WatchTask extends XNapTask
402 	{
403 		public void run() 
404 		{
405 			synchronized (sm) {
406 				if (sm.getState() == State.REQUESTING) {
407 					failed = true;
408 					sm.setState(State.FINISHED, "timeout");
409 				}
410 			}
411 			stateChanged();
412 			handler.stateChanged(OpenNapDirectBrowse.this);
413 		}
414 	}
415 
416 	private class StopAction extends AbstractStopAction 
417 	{
418 		public void actionPerformed(ActionEvent e) 
419 		{
420 			stop();
421 		}
422 	}
423 
424 	private class OpenNapDirectBrowseDownloadRunner implements Runnable
425 	{
426 		private boolean die;
427 		
428 		private Socket socket;
429 		private InputStream in;
430 		private OutputStream out;
431 
432 		public OpenNapDirectBrowseDownloadRunner(BrowseDownloadSocket b)
433 		{
434 			if (b != null) {
435 				socket = b.socket;
436 				in = b.in;
437 			}
438 		}
439 
440 		public void run()
441 		{
442 			try {
443 				if (socket != null) {
444 					connect();
445 				}
446 				else {
447 					connect(user.getHost(), user.getPort());
448 				}
449 
450 				setState(State.DOWNLOADING);
451 
452 				download();
453 
454 				setState(State.FINISHED);
455 			}
456 			catch (IOException e) {
457 				failed = true;
458 				setState(State.FINISHED, NetHelper.getErrorMessage(e));
459 			}
460 			catch (InterruptedException e) {
461 				failed = true;
462 				setState(State.FINISHED, XNap.tr("Stopped"));
463 			}
464 			finally {
465 				close();
466 			}
467 		}		       
468 
469 		public void stop()
470 		{
471 			die = true;
472 		}
473 
474 		private void close() 
475 		{
476 			try {
477 				if (socket != null)
478 					socket.close();
479 				if (in != null)
480 					in.close();
481 				if (out != null)
482 					out.close();
483 			} 
484 			catch (IOException e) {
485 			}
486 		}
487 
488 		private void connect(String host, int port) throws IOException
489 		{
490 			logger.debug("connecting to " + host + ":" + port 
491 						 + " for direct browse");
492 			socket = new Socket(host, port);
493 			socket.setSoTimeout(SOCKET_TIMEOUT);
494 			
495 			out = socket.getOutputStream();
496 			in = new BufferedInputStream(socket.getInputStream());
497 	
498 			// read magic number '1'
499 			logger.debug("reading magic number");
500 			char c = (char)in.read();
501 			if (c != '1') {
502 				throw new IOException(XNap.tr("Invalid request"));
503 			}
504 
505 			write("GETLIST");
506 			
507 			byte data[] = new byte[2048];
508 			in.mark(2048);
509 			int i = in.read(data);
510 
511 			if (i > 0) {
512 				String nick = new String(data, 0, i);
513 				int j = nick.indexOf("\n");
514 				if (j == -1) {
515 					// read '\n'
516 					in.read();
517 				}
518 				else {
519 					nick = nick.substring(0, j).trim();
520 					
521 					in.reset();
522 					in.skip(j + 1);
523 				}
524 				
525 				if (!nick.equals(user.getName())) {
526 					throw new IOException("Invalid user: " + nick);
527 				}
528 			}
529 			else {
530 				throw new IOException("Socket error");
531 			}
532 		} 
533 
534 		private void connect() throws IOException
535 		{
536 			socket.setSoTimeout(SOCKET_TIMEOUT);
537 			
538 			out = socket.getOutputStream();
539 	    }
540 
541 		private void download() throws IOException, InterruptedException
542 		{
543 			BufferedReader reader
544 				= new BufferedReader(new InputStreamReader(in));
545 
546 			int count = user.getFileCount();
547 			if (count != -1) {
548 				segment = new DefaultSegment(count);
549 			}
550 
551 			String s;
552 			count = 0;
553 			while ((s = reader.readLine()) != null) {
554 				if (die) {
555 					throw new InterruptedException();
556 				}
557 				else if (s.length() == 0) {
558 					break;
559 				}
560 
561 				bytesTransferred += s.getBytes().length;
562 				if (segment != null) {
563 					segment.commit(1);
564 				}
565 
566 				QuotedStringTokenizer t = new QuotedStringTokenizer(s);
567 				
568 				if (t.countTokens() < 6) {
569 					continue;
570 				}
571 
572 				try {
573 					String filename = t.nextToken();
574 					String md5 = t.nextToken();
575 					long size = Long.parseLong(t.nextToken());
576 					int bitrate = Integer.parseInt(t.nextToken());
577 					int frequency = Integer.parseInt(t.nextToken());
578 					int length = Integer.parseInt(t.nextToken());
579 
580 					SearchResult result = new OpenNapSearchResult
581 						(null, size, bitrate, frequency, length,
582 						 user, filename, md5);
583 					handler.resultReceived(result);
584 				}
585 				catch (NumberFormatException e) {
586 				}
587 			}
588 		}
589 
590 		private void write(String message) throws IOException
591 		{
592 			logger.debug("> " + message);
593 			out.write(message.getBytes());
594 			out.flush();
595 		}
596 		
597 	}    
598 
599 }
600