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.gift.net;
21  
22  import java.io.BufferedInputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.net.ConnectException;
27  import java.net.Socket;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.Vector;
33  
34  import org.apache.log4j.Logger;
35  import org.xnap.XNap;
36  import org.xnap.net.NetHelper;
37  import org.xnap.peer.Peer;
38  import org.xnap.plugin.gift.GiFTPlugin;
39  import org.xnap.plugin.gift.net.event.ControlEvent;
40  import org.xnap.plugin.gift.net.event.SearchControlEvent;
41  import org.xnap.plugin.gift.net.event.SearchItemEvent;
42  import org.xnap.plugin.gift.net.event.ShareItemEvent;
43  import org.xnap.plugin.gift.net.event.UploadUpdatedEvent;
44  import org.xnap.plugin.gift.net.lexer.Command;
45  import org.xnap.plugin.gift.net.lexer.StreamLexer;
46  import org.xnap.transfer.DownloadManager;
47  import org.xnap.transfer.UploadManager;
48  import org.xnap.util.FiniteStateMachine;
49  import org.xnap.util.IllegalOperationException;
50  import org.xnap.util.Scheduler;
51  import org.xnap.util.State;
52  import org.xnap.util.XNapTask;
53  
54  /***
55   * Handles a connection to a single giFT daemon.
56   *
57   * @author Tammo van Lessen
58   * @author Steffen Pingel
59   */
60  public class GiFTDaemon {
61  	
62  	/***
63  	 * The poll intervall for network stats.
64  	 */
65  	public static final int NETWORK_STATS_INTERVAL = 3 * 60 * 1000; // 3 min
66  
67      private static final Hashtable TRANSITION_TABLE;
68      static {
69  		State[][] table = new State[][] {
70  			{ org.xnap.util.State.DISCONNECTED,  
71  			  org.xnap.util.State.CONNECTING, },
72  			{ org.xnap.util.State.CONNECTING, 
73  			  org.xnap.util.State.CONNECTED, org.xnap.util.State.DISCONNECTED },
74  			{ org.xnap.util.State.CONNECTED, 
75  			  org.xnap.util.State.DISCONNECTING, },
76  			{ org.xnap.util.State.DISCONNECTING,
77  			  org.xnap.util.State.DISCONNECTED },
78  		};
79  
80  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
81      }
82  
83  	long NetworkStatsTimer = 0;
84  	private Logger logger = Logger.getLogger(GiFTDaemon.class);
85  	private Hashtable downloads;
86  	private Hashtable uploads;
87  	private Hashtable searches;
88  	private String host;
89  	private String serverName;
90  	private String serverVersion;
91  	private String user;
92  	private Vector listeners = new Vector();
93  	private int port;
94      private StateMachine sm = new StateMachine();
95      private String message;
96  	private String username;
97  	private String stats;
98  	private String verboseMessage;
99  
100 	//--- Constructors ---
101 
102 	/***
103 	 * 
104 	 */
105 	public GiFTDaemon(String host, int port, String user) 
106 	{
107 		this.host = host;
108 		this.port = port;
109 		this.user = user;
110 	}
111 
112 	//--- Methods ---
113 
114 	/***
115 	 * Adds an event listener.
116 	 *
117 	 * @param listener the listener
118 	 */
119 	public void addDaemonListener(GiFTDaemonListener listener) 
120 	{
121 		listeners.add(listener);
122 	}
123 
124 	public void addDownload(GiFTSearchResult sr) 
125 	{
126 		Command cmd = new Command("addsource");
127 		cmd.addKey("hash", (String)sr.getHash());
128 		cmd.addKey("size", Long.toString(sr.getFilesize()));
129 		cmd.addKey("url", sr.getUrl());
130 		cmd.addKey("user", ((GiFTUser) sr.getPeer()).getUsername());
131 		// TODO
132 		cmd.addKey("save", sr.getShortFilename());
133 		queueCommand(cmd);
134 	}
135 
136 	public void changeDownload(GiFTDownloadContainer dc, String action) 
137 	{
138 		Command cmd = new Command("transfer");
139 		cmd.setCommandArgument(dc.getGID());
140 		cmd.addKey("action", action);
141 		queueCommand(cmd);
142 	}
143 
144 	public void changeSearch(GiFTSearch s, String action) 
145 	{
146 		Command cmd = new Command("search");
147 		cmd.setCommandArgument(s.getGID());
148 		cmd.addKey("action", action);
149 		queueCommand(cmd);
150 	}
151 
152 	public void changeUpload(GiFTUpload u, String action) 
153 	{
154 		Command cmd = new Command("transfer");
155 		cmd.setCommandArgument(u.getGID());
156 		cmd.addKey("action", action);
157 		queueCommand(cmd);
158 	}
159 
160 	public void deleteSource(GiFTDownloadContainer dc, String url) 
161 	{
162 		Command cmd = new Command("delsource");
163 		cmd.setCommandArgument(dc.getGID());
164 		cmd.addKey("url", url);
165 		queueCommand(cmd);
166 	}
167 
168 //	private void fireMessageReceived(String message) 
169 //	{
170 //		GiFTDaemonListener[] l 
171 //			= (GiFTDaemonListener[])listeners.toArray(new GiFTDaemonListener[0]);
172 //		for (int i = 0; i < l.length; i++) {
173 //			l[i].messageReceived(this, message);
174 //		}
175 //	}
176 //
177 //	private void fireStatsUpdated(String stats)
178 //	{
179 //		GiFTDaemonListener[] l
180 //			= (GiFTDaemonListener[])listeners.toArray(new GiFTDaemonListener[0]);
181 //		for (int i = 0; i < l.length; i++) {
182 //			l[i].statsUpdated(this, stats);
183 //		}
184 //	}
185 
186 	private void fireStatusChanged() 
187 	{
188 		GiFTDaemonListener[] l
189 			= (GiFTDaemonListener[])listeners.toArray(new GiFTDaemonListener[0]);
190 		for (int i = 0; i < l.length; i++) {
191 			l[i].statusChanged(this);
192 		}
193 	}
194 
195 	protected GiFTDownloadContainer getDownloadContainer
196 		(String filename, String hash, long size) 
197 	{
198 		GiFTDownloadContainer dc = (GiFTDownloadContainer)downloads.get(hash);
199 		if (dc == null || dc.isDone()) {
200 			dc = new GiFTDownloadContainer(this, filename, hash, size);
201 			downloads.put(hash, dc);
202 			DownloadManager.getInstance().add(dc);
203 		}
204 		return dc;
205 	}
206 
207 	public String getHost()
208 	{
209 		return host;
210 	}
211 
212 	public String getMessage() 
213 	{
214 		return message;
215 	}
216 
217 	public int getPort()
218 	{
219 		return port;
220 	}
221 	
222 //	protected GiFTUpload getUpload(String filename, String hash, long size) 
223 //	{
224 //		GiFTUpload u = (GiFTUpload)uploads.get(hash);
225 //		if (u == null) {
226 //			u = new GiFTUpload(filename, hash, size);
227 //			uploads.put(hash, u);
228 //			UploadManager.getInstance().add(u);
229 //		}
230 //		return u;
231 //	}
232 
233 	public String getUsername()
234 	{
235 		return username;
236 	}
237 
238 	public String getStats() 
239 	{
240 		return stats;
241 	}
242 
243 	public String getStatus()
244 	{
245 		StringBuffer sb = new StringBuffer();
246 		sb.append("<html>");
247 		sb.append(getServerInfo());
248 		if (getStats() != null) {
249 			sb.append("<br>");
250 			sb.append(getStats());
251 		}
252 		if (getMessage() != null) {
253 			sb.append("<hr");
254 			sb.append(getMessage());
255 		}
256 		return sb.toString();
257 	}
258 
259 	/***
260 	 * Returns giFT server's name and version as String
261 	 *
262 	 * @return server info
263 	 */
264 	public String getServerInfo() 
265 	{
266 		if (sm.getState() == org.xnap.util.State.CONNECTED && serverName != null) {
267 			// only send version if set, daemons compiled from cvs for
268 			// instance do not send a version
269 			return "<b>" + serverName + "</b>" 
270 				+ ((serverVersion != null) ? " v" + serverVersion : "");
271 		}
272 		else {
273 			return sm.getDescription();
274 		}
275 	}
276 
277 	public String getVerboseMessage()
278 	{
279 		return verboseMessage;
280 	}
281 
282 	public boolean isConnected()
283 	{
284 		return sm.getState() == org.xnap.util.State.CONNECTED;
285 	}
286 
287 	public boolean isDisconnected()
288 	{
289 		return sm.getState() == org.xnap.util.State.DISCONNECTED;
290 	}
291 	
292 	/***
293 	 * Searches for more sources for dc.
294 	 */
295 	public void locate(GiFTDownloadContainer dc) 
296 	{
297 		String gid = GIDManager.createGID() + "";
298 		searches.put(gid, dc);
299 
300 		Command cmd = new Command("locate");
301 		cmd.setCommandArgument(Integer.toString(dc.hashCode()));
302 		cmd.addKey("query", dc.getHash());
303 		queueCommand(cmd);
304 	}
305 
306 	protected void queueCommand(Command cmd) 
307 	{
308 		synchronized (sm) {
309 			if (sm.getState() == org.xnap.util.State.CONNECTED) {
310 				sm.getWriter().enqueue(cmd);
311 			}
312 		}
313 	}
314 
315 	/***
316 	 * Removes an event listener.
317 	 *
318 	 * @param listener
319 	 */
320 	public void removeDaemonListener(GiFTDaemonListener listener) 
321 	{
322 		listeners.remove(listener);
323 	}
324 
325 	/***
326 	 * Forces giFT to send network stats
327 	 */
328 	public synchronized void updateNetworkStats() 
329 	{
330 		Command cmd = new Command("stats");
331 		queueCommand(cmd);
332 	}
333 
334 	/***
335 	 * Forces giFT to list its shares
336 	 */
337 	public void updateShareListing() 
338 	{
339 		Command cmd = new Command("shares");
340 		queueCommand(cmd);
341 	}
342 
343 	/***
344 	 * Adds a new search
345 	 *
346 	 * @param sf SearchFilter
347 	 */
348 	public void search(GiFTSearch s) 
349 	{
350 		String gid = GIDManager.createGID() + "";
351 		searches.put(gid, s);
352 		s.setGID(gid);
353 
354 		Command cmd = new Command("search");
355 		cmd.setCommandArgument(gid);
356 		cmd.addKey("query", s.getFilter().getText());
357 		queueCommand(cmd);
358 
359 		s.searchStarted(new SearchControlEvent(ControlEvent.STARTED));
360 	}
361 
362 	/***
363 	 * Sets giFT host
364 	 *
365 	 * @param host
366 	 */
367 	public void setHost(String host) 
368 	{
369 		this.host = host;
370 	}
371 
372 	/***
373 	 * Sets giFT port
374 	 *
375 	 * @param port
376 	 */
377 	public void setPort(int port) 
378 	{
379 		this.port = port;
380 	}
381 
382     void setState(State newState, String description)
383     {
384 		sm.setState(newState, description);
385 		fireStatusChanged();
386     }
387 
388     void setState(State newState)
389     {
390 		sm.setState(newState);
391 		fireStatusChanged();
392     }
393 
394     void setStateDescription(String description)
395     {
396 		sm.setDescription(description);
397 		fireStatusChanged();
398     }
399 
400 	/***
401 	 * Sets giFT user name
402 	 *
403 	 * @param user
404 	 */
405 	public void setUsername(String username) {
406 		this.username = username;
407 	}
408 	
409 	public void setVerboseMessage(Exception e)
410 	{
411 		if (e == null) {
412 			verboseMessage = null;
413 		}
414 		else if (e instanceof ConnectException) {
415 			// connection refused
416 			StringBuffer sb = new StringBuffer();
417 			sb.append("<br><p>");
418 			sb.append(XNap.tr("Could not connect to giFT daemon. This can have several reasons:"));
419 			sb.append("<ol><li>");
420 			sb.append(XNap.tr("<b>giFT is not installed</b>. This plugin requires an external programm called giFT that handles the network connection. Please see http://gift.sf.net/ for more information and download links."));
421 			sb.append("<li>");
422 			sb.append(XNap.tr("The plugin <b>settings are incorrect</b>, e.g. the giFT daemon port is wrong. Please check the settings under Settings -> Configure Plugins."));
423 			sb.append("<li>");
424 			sb.append(XNap.tr("The <b>giFT daemon is not running</b>. Please click the Start button on the giFT daemon panel."));
425 			sb.append("</ol>");
426 			verboseMessage = sb.toString();
427 		}
428 	}
429 	
430 	/***
431 	 * Connects to the daemon.
432 	 */
433 	public void start() 
434 	{
435 		setState(org.xnap.util.State.CONNECTING);
436 	}
437 
438 	/***
439 	 * Disconnects the daemon.
440 	 */
441 	public void stop(boolean killGiFT) 
442 	{
443 		Command cmd 
444 			= (killGiFT) ? new Command("quit") : new Command("detach");
445 		queueCommand(cmd);
446 	}
447 
448 	/***
449 	 * Forces giFT to sync its shares index
450 	 */
451 	public void syncShares() 
452 	{
453 		Command cmd = new Command("share");
454 		cmd.addKey("action", "sync");
455 		queueCommand(cmd);
456 	}
457 
458 	/***
459 	 * Dispatches incomming commands.
460 	 */
461 	private void dispatchCommand(Command cmd) 
462 	{
463 		if (cmd.getCommand().equalsIgnoreCase("ATTACH")) {
464 			serverName = cmd.getKey("server");
465 			serverVersion = cmd.getKey("version");
466 
467 			fireStatusChanged();
468 		} 
469 		else if (cmd.getCommand().equalsIgnoreCase("STATS")) {
470 			StringBuffer sb = new StringBuffer();
471 
472 			Vector subCommands = cmd.getSubCommands();
473 			for (int i = 0; i < subCommands.size(); i++) {
474 				Command subCmd = (Command)subCommands.get(i);
475 				if (subCmd.getKey("files") != null
476 					&& subCmd.getKey("size") != null) {
477 
478 					try {
479 						String protocol 
480 							=  (subCmd.getCommand().equalsIgnoreCase("gift"))
481 							? XNap.tr("Local")
482 							: subCmd.getCommand();
483 
484 						String s;
485 						if (subCmd.getKey("users") != null) {
486 							s = XNap.tr
487 								("{0}: {1} Files, {2} Users, {3} GB",
488 								 protocol,
489 								 new Long((String)subCmd.getKey("files")),
490 								 new Long((String)subCmd.getKey("users")),
491 								 new Float((String)subCmd.getKey("size")));
492 						}
493 						else {
494 							s = XNap.tr
495 								("{0}: {1} Files, {2} GB",
496 								 protocol,
497 								 new Long((String)subCmd.getKey("files")),
498 								 new Float((String)subCmd.getKey("size")));
499 						}
500 
501 						if (sb.length() > 0) {
502 							sb.append("<br>");
503 						}
504 						sb.append(s);
505 						
506 					}
507 					catch (NumberFormatException e) {
508 					}
509 				}
510 			}
511 
512 			this.stats = sb.toString();
513 			fireStatusChanged();
514 		} 
515 		else if (cmd.getCommand().equalsIgnoreCase("ITEM")) {
516 			if (cmd.getCommandArgument() == null) {
517 				if (cmd.hasKeys()) {
518 					// received share item 
519 					ShareItemEvent se = new ShareItemEvent();
520 					se.setPath(cmd.getKey("path"));
521 
522 					try {
523 						se.setSize(Long.parseLong(cmd.getKey("size")));
524 					} catch (Exception e) {
525 					}
526 
527 					se.setMime(cmd.getKey("mime"));
528 					se.setHash(cmd.getKey("hash"));
529 
530 					Command meta = cmd.getSubCommandByName("META");
531 
532 					if (meta != null) {
533 						Enumeration keys = meta.getKeys();
534 
535 						while (keys.hasMoreElements()) {
536 							String key = (String) keys.nextElement();
537 							se.addMetaItem(key, meta.getKey(key));
538 						}
539 					}
540 				} 
541 				else {
542 					// finished receiving shares
543 				}
544 			} 
545 			else {
546 				if (cmd.hasKeys()) {
547 					// received search item					
548 					if  ((cmd.getKey("file") == null) ||
549 						 (cmd.getKey("hash") == null) ||
550 						 (cmd.getKey("node") == null) ||
551 						 (cmd.getKey("url") == null)) { 
552 						logger.debug("Received invalied search result");
553 						return;
554 					}
555 					
556 					long size = -1;
557 					try {
558 						size = Long.parseLong(cmd.getKey("size"));
559 					}
560 					catch (NumberFormatException e) {
561 					}
562 
563 					int score = 1;
564 					try {
565 						score = Integer.parseInt(cmd.getKey("availability"));
566 					} 
567 					catch (NumberFormatException e) {
568 					}
569 
570 					Hashtable meta = new Hashtable();
571 					Command metaCmd = cmd.getSubCommandByName("META");
572 					if (metaCmd != null) {
573 						Enumeration keys = metaCmd.getKeys();
574 
575 						while (keys.hasMoreElements()) {
576 							String key = (String) keys.nextElement();
577 							meta.put(key, metaCmd.getKey(key));
578 						}
579 					}
580 
581 					Peer user = new GiFTUser(cmd.getKey("user"));
582 					
583 					GiFTSearchResult sr =
584 						new GiFTSearchResult
585 							(this,
586 							 size,
587 							user,
588 							cmd.getKey("file"),
589 							cmd.getKey("hash"),
590 							cmd.getKey("node"),
591 							cmd.getKey("mime"),
592 							cmd.getKey("url"),
593 							score,
594 							meta);
595 
596 					Object searcher = searches.get(cmd.getCommandArgument());
597 					logger.debug(searcher);
598 					if (searcher instanceof GiFTSearch) { 
599 						GiFTSearch search =
600 							(GiFTSearch) searches.get(cmd.getCommandArgument());
601 						search.searchItemReceived(
602 							new SearchItemEvent(search.getFilter(), sr));
603 					}
604 					else if (searcher instanceof GiFTDownloadContainer){
605 						addDownload(sr);
606 					}
607 				} 
608 				else {
609 					// search finished
610 					GiFTSearch search 
611 						= (GiFTSearch)searches.get(cmd.getCommandArgument());
612 					if (search != null) {
613 						searches.remove(cmd.getCommandArgument());
614 						search.searchFinished
615 							(new SearchControlEvent(ControlEvent.FINISHED));
616 					}
617 				}
618 			}
619 		} 
620 		else if (cmd.getCommand().equalsIgnoreCase("ADDDOWNLOAD")) {
621 			String filename = cmd.getKey("file");
622 			String hash = cmd.getKey("hash");
623 			long size = -1;
624 
625 			try {
626 				size = Long.parseLong(cmd.getKey("size"));
627 			} catch (Exception e) {
628 			}
629 
630 			long transmit = -1;
631 
632 			try {
633 				transmit = Long.parseLong(cmd.getKey("transmit"));
634 			} catch (Exception e) {
635 			}
636 
637 			GiFTDownloadContainer dc =
638 				getDownloadContainer(filename, hash, size);
639 
640 			dc.setState(cmd.getKey("state"));
641 			dc.setGID(cmd.getCommandArgument());
642 			dc.setTotalBytesTransferred(transmit);
643 			dc.setOffset(transmit);
644 
645 			addSources(cmd, dc);
646 		} 
647 		else if (cmd.getCommand().equalsIgnoreCase("CHGDOWNLOAD")) {
648 			GiFTDownloadContainer dc =
649 				(GiFTDownloadContainer)downloads.get(cmd.getKey("hash"));
650 			if (dc != null) {
651 				if (cmd.getKey("throughput") != null) {
652 					try {
653 						dc.setCurrentRate
654 							(Long.parseLong(cmd.getKey("throughput")));
655 					} 
656 					catch (NumberFormatException e) {
657 					}
658 				}
659 
660 				if (cmd.getKey("transmit") != null) {
661 					try {
662 						dc.setTotalBytesTransferred
663 							(Long.parseLong(cmd.getKey("transmit")));
664 					} 
665 					catch (NumberFormatException e) {
666 					}
667 				}
668 				
669 				dc.setState(cmd.getKey("state"));
670 
671 				addSources(cmd, dc);
672 			}
673 		} 
674 		else if (cmd.getCommand().equalsIgnoreCase("ADDUPLOAD")) {
675 			String filename = cmd.getKey("file");
676 			String hash = cmd.getKey("hash");
677 			long size = -1;
678 
679 			try {
680 				size = Long.parseLong(cmd.getKey("size"));
681 			} catch (Exception e) {
682 			}
683 	
684 			long transmit = -1;
685 	
686 			try {
687 				transmit = Long.parseLong(cmd.getKey("transmit"));
688 			} catch (Exception e) {
689 			}
690 	
691 			GiFTUpload u = (GiFTUpload)uploads.get(hash);
692 			if (u == null) {
693 				u = new GiFTUpload(new GiFTUser(cmd.getKey("user")),
694 						cmd.getKey("url"), size, transmit);
695 				
696 				u.setGID(cmd.getCommandArgument());
697 				uploads.put(hash, u);
698 				UploadManager.getInstance().add(u);
699 			}
700 	
701 			u.setStatus(cmd.getKey("state"));
702 	
703 			//fireEvent(new DownloadAddedEvent(dc));
704 		} else if (cmd.getCommand().equalsIgnoreCase("CHGUPLOAD")) {
705 			String filename = cmd.getKey("file");
706 			String hash = cmd.getKey("hash");
707 			long size = -1;
708 	
709 			try {
710 				size = Long.parseLong(cmd.getKey("size"));
711 			} catch (Exception e) {
712 			}
713 	
714 			long transmit = -1;
715 	
716 			try {
717 				transmit = Long.parseLong(cmd.getKey("transmit"));
718 			} catch (Exception e) {
719 			}
720 	
721 			long elapsed = -1;
722 	
723 			try {
724 				elapsed = Long.parseLong(cmd.getKey("elapsed"));
725 			} catch (Exception e) {
726 			}
727 	
728 			long throughput = -1;
729 	
730 			try {
731 				throughput = Long.parseLong(cmd.getKey("throughput"));
732 			} catch (Exception e) {
733 			}
734 	
735 			String state = cmd.getKey("state");
736 			GiFTUpload u =
737 				(GiFTUpload) uploads.get(cmd.getKey("hash"));
738 	
739 			if (u != null) {
740 				UploadUpdatedEvent due = new UploadUpdatedEvent();
741 				due.setElapsed(elapsed);
742 				due.setFilename(filename);
743 				due.setHash(hash);
744 				due.setSize(size);
745 				due.setState(state);
746 				due.setThroughput(throughput);
747 				due.setTransmit(transmit);
748 	
749 				// fire event directly
750 				u.uploadUpdated(due);
751 			}
752 		} else if (cmd.getCommand().equalsIgnoreCase("DELUPLOAD")) {
753 				Enumeration it = uploads.elements();
754 				while (it.hasMoreElements()) {
755 					GiFTUpload u = (GiFTUpload) it.nextElement();
756 					if (u.getGID().equals(cmd.getCommandArgument())) {
757 						u.uploadCanceled();
758 					}
759 				}
760 		} else if (cmd.getCommand().equalsIgnoreCase("DELDOWNLOAD")) {
761 				Enumeration it = downloads.elements();
762 				while (it.hasMoreElements()) {
763 					GiFTDownloadContainer dc = (GiFTDownloadContainer) it.nextElement();
764 					if (dc.getGID().equals(cmd.getCommandArgument())) {
765 						dc.deleted();
766 					}
767 				}
768 		} else if (cmd.getCommand().equalsIgnoreCase("MESSAGE")) {
769 			message = cmd.getCommandArgument();
770 			fireStatusChanged();
771 		}
772 	}
773 
774 
775 	private void addSources(Command cmd, GiFTDownloadContainer dc) 
776 	{
777 		dc.markChildren();
778 		
779 		Vector v = cmd.getAllSubCommandsByName("source");
780 		Iterator i = v.iterator();
781 		while (i.hasNext()) {
782 			Command c = (Command)i.next();
783 			GiFTDownload d = dc.getSourceByURL(c.getKey("url"));
784 			if (d == null) {
785 				d = dc.addSource(new GiFTUser(c.getKey("user")),
786 								 c.getKey("url"));
787 			}
788 
789 			if (c.getKey("start") != null
790 				&& c.getKey("transmit") != null
791 				&& c.getKey("total") != null) {
792 
793 				try {
794 					d.updated(Long.parseLong(c.getKey("start")),
795 							  Long.parseLong(c.getKey("transmit")),
796 							  Long.parseLong(c.getKey("total")));	  
797 				} 
798 				catch (NumberFormatException e) {
799 				}
800 			}
801 
802 			d.setMarked(false);
803 			d.setState(c.getKey("statusgrl"), c.getKey("status"));
804 		}
805 
806 		dc.orphanMarkedChildren();
807 	}
808 
809 	private class ReaderThread extends Thread
810 	{
811 		private InputStream in;
812 		private OutputStream out;
813 		private Socket socket;
814 		private boolean stopped = false;
815 
816 		public ReaderThread()
817 		{
818 			super("GiFTDeamonReader " + getHost() + ":" + getPort());
819 		}
820 
821 		public void die() 
822 		{
823 			stopped = true;
824 			interrupt();
825 		}
826 
827 		public OutputStream getOutputStream()
828 		{
829 			return out;
830 		}
831 
832 		public void run()
833 		{
834 			setVerboseMessage(null);
835 			try {
836 				logger.debug("Connecting to giFT damon @" 
837 							 + getHost() + ":" + getPort());
838 				Socket socket = new Socket(getHost(), getPort());
839 				socket.setSoTimeout(NETWORK_STATS_INTERVAL * 2);
840 								
841 				in = new BufferedInputStream(socket.getInputStream());
842 				out = socket.getOutputStream();
843 			} 
844 			catch (IOException e) {
845 				setState(org.xnap.util.State.DISCONNECTED, NetHelper.getErrorMessage(e));
846 				setVerboseMessage(e);
847 				return;
848 			}
849 
850 			setState(org.xnap.util.State.CONNECTED);
851 
852 			try {				
853 				StreamLexer lexer = new StreamLexer(in);
854 
855 				while (!stopped) {
856 					Command cmd = readNextCommand();
857 					if (cmd != null) {
858 						if (XNap.isRunFromCvs()) {
859 							logger.debug("< " + cmd.print());
860 						}
861 						
862 						dispatchCommand(cmd);
863 					}
864 				}
865 			}
866 			catch (IOException e) {
867  				try {
868 					setState(org.xnap.util.State.DISCONNECTING, 
869 							 NetHelper.getErrorMessage(e));
870 				}
871 				catch (IllegalOperationException e2) {
872 					// disconnect is already in progress
873 				}
874 			} 
875 			finally {
876 				try {
877 					if (socket != null) {
878 						socket.close();
879 					}
880 				} 
881 				catch (IOException e) {
882 				}
883 			}
884 			sm.readerStopped();
885 		}
886 
887 		private Command readNextCommand() throws IOException 
888 		{
889 			StringBuffer sb = new StringBuffer();
890 			while (!stopped) {
891 				int cint = in.read();
892 				if (cint == -1) {
893 					throw new IOException(XNap.tr("Socket closed"));
894 				}
895 				else if (cint == ';') {
896 					break;
897 				}
898 				sb.append((char)cint);
899 			}
900 
901 			Command cmd = new Command();
902 			return (stopped || !cmd.parse(sb.toString().trim() + ";"))
903 				? null
904 				: cmd;
905 		}
906 
907 	}
908 
909 	private class WriterThread extends Thread
910 	{
911 		private OutputStream out;
912 		private LinkedList outQueue = new LinkedList();
913 		private boolean stopped = false;
914 
915 		private WriterThread(OutputStream out)
916 		{
917 			super("GiFTDeamonWriter " + getHost() + ":" + getPort());
918 
919 			this.out = out;
920 		}
921 
922 		public synchronized void enqueue(Command cmd)
923 		{
924 			outQueue.addLast(cmd);
925 			notify();
926 		}
927 
928 		public void die() 
929 		{
930 			stopped = true;
931 			interrupt();
932 		}
933 
934 		public void run()
935 		{
936 			try {
937 				while (!stopped) {
938 					synchronized (this) {
939 						while (!stopped && outQueue.isEmpty()) {
940 							try {
941 								this.wait();
942 							}
943 							catch (InterruptedException e) {
944 							}
945 						}
946 					}
947 
948 					if (stopped) {
949 						break;
950 					}
951 
952 					Command cmd = (Command) outQueue.removeFirst();					
953 					if (XNap.isRunFromCvs()) {
954 						logger.debug("> " + cmd.print());
955 					}
956 
957 					out.write(cmd.print().getBytes());
958 				}
959 			}
960 			catch (IOException e) {
961 				try {
962 					setState(org.xnap.util.State.DISCONNECTING, 
963 							 NetHelper.getErrorMessage(e));
964 				}
965 				catch (IllegalOperationException e2) {
966 					// disconnect is already in progress
967 				}
968 			}
969 			sm.writerStopped();
970 		}
971 
972 	}
973 
974 	private class StateMachine extends FiniteStateMachine
975     {
976 
977 		private StatsRequestorTask statsRequestor;
978 		private ReaderThread reader;
979 		private WriterThread writer;
980 
981 		public StateMachine()
982 		{
983 			super(org.xnap.util.State.DISCONNECTED, TRANSITION_TABLE);
984 		}
985 
986 		public WriterThread getWriter()
987 		{
988 			return writer;
989 		}
990 
991 		public void readerStopped()
992 		{
993 			synchronized (this) {
994 				reader = null;
995 				if (writer == null) {
996 					GiFTDaemon.this.setState(org.xnap.util.State.DISCONNECTED);
997 				}
998 			}
999 		}
1000 
1001 		public void writerStopped()
1002 		{
1003 			boolean disconnected = false;
1004 			synchronized (this) {
1005 				writer = null;
1006 				if (reader == null) {
1007 					disconnected = true;
1008 				}
1009 			}
1010 			if (disconnected) {
1011 				GiFTDaemon.this.setState(org.xnap.util.State.DISCONNECTED);
1012 			}
1013 
1014 		}
1015 
1016 		protected synchronized void stateChanged(State oldState,
1017 												 State newState)
1018 		{
1019 			if (newState == org.xnap.util.State.CONNECTING) {
1020 				downloads = new Hashtable();
1021 				uploads = new Hashtable();
1022 				searches = new Hashtable();
1023 
1024 				message = null;
1025 				stats = null;
1026 
1027 				reader = new ReaderThread();
1028 				reader.start();
1029 			}
1030 			else if (newState == org.xnap.util.State.CONNECTED) {
1031 				writer = new WriterThread(reader.getOutputStream());
1032 				writer.start();
1033 						
1034 				Command cmd = new Command("attach");
1035 				cmd.addKey("client", "XNap");
1036 				cmd.addKey("version", 
1037 						   GiFTPlugin.getInstance().getInfo().getVersion());
1038 				if (username != null) {
1039 					cmd.addKey("profile", username);
1040 				}
1041 				queueCommand(cmd);
1042 
1043 				statsRequestor = new StatsRequestorTask();
1044 				Scheduler.run(0, NETWORK_STATS_INTERVAL, statsRequestor);
1045 			}
1046 			else if (newState == org.xnap.util.State.DISCONNECTING) {
1047 				statsRequestor.cancel();
1048 				statsRequestor = null;
1049 
1050 				reader.die();
1051 				if (writer != null) {
1052 					writer.die();
1053 				}
1054 			}
1055 			else if (newState == org.xnap.util.State.DISCONNECTED) {
1056 				for (Iterator i = downloads.values().iterator(); i.hasNext();) {
1057 					DownloadManager.getInstance().remove
1058 						((GiFTDownloadContainer)i.next());
1059 				}
1060 				for (Iterator i = uploads.values().iterator(); i.hasNext();) {
1061 					UploadManager.getInstance().remove
1062 						((GiFTUpload)i.next());
1063 				}
1064 				// fix: remove uploads
1065 			}
1066 		}
1067 	
1068     }
1069 
1070 	private static class GIDManager {
1071 		
1072 		public static int MIN_GID = 1;
1073 		/***
1074 		 * The maximum allowed gid to be used by the client as a session-id
1075 		 * as confirmed by jasta in irc chat.
1076 		 */
1077 		public static int MAX_GID = 32767; // 2^15 - 1
1078 
1079 		public static int last = MIN_GID - 1;
1080 
1081 		/***
1082 		 * Returns a unique id.
1083 		 */
1084 		public static int createGID()
1085 		{
1086 			return (++last > MAX_GID) ?	last = MIN_GID : last;
1087 		}
1088 
1089 	}
1090 
1091     /***
1092      * Resends the download request in regular intervals.
1093      */
1094 	private class StatsRequestorTask extends XNapTask
1095 	{
1096 
1097 		public StatsRequestorTask() 
1098 		{
1099 		}
1100 
1101 		public void run() 
1102 		{
1103 			updateNetworkStats();
1104 		}
1105 
1106     }
1107 
1108 }