1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
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
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
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
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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
223
224
225
226
227
228
229
230
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
268
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
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
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
543 }
544 }
545 else {
546 if (cmd.hasKeys()) {
547
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
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
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
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
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
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
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;
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 }