1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.plugin.opennap.net;
21
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.StringTokenizer;
31
32 import org.apache.log4j.Logger;
33 import org.xnap.XNap;
34 import org.xnap.event.ListListener;
35 import org.xnap.event.ListSupport;
36 import org.xnap.event.StatusListener;
37 import org.xnap.io.Library;
38 import org.xnap.io.MetaInfoFile;
39 import org.xnap.io.MetaInfoManager;
40 import org.xnap.net.NetHelper;
41 import org.xnap.plugin.opennap.OpenNapPlugin;
42 import org.xnap.plugin.opennap.net.msg.ExceptionListener;
43 import org.xnap.plugin.opennap.net.msg.MessageHandler;
44 import org.xnap.plugin.opennap.net.msg.client.AddHotlistEntryMessage;
45 import org.xnap.plugin.opennap.net.msg.client.ShareFileMessage;
46 import org.xnap.plugin.opennap.user.OpenNapGlobalUser;
47 import org.xnap.plugin.opennap.util.OpenNapPreferences;
48 import org.xnap.search.SearchManager;
49 import org.xnap.util.Formatter;
50 import org.xnap.util.IllegalOperationException;
51 import org.xnap.util.Notifier;
52 import org.xnap.util.PortRange;
53 import org.xnap.util.Range;
54 import org.xnap.util.Scheduler;
55 import org.xnap.util.XNapTask;
56
57 public class OpenNapServerManager implements PropertyChangeListener {
58
59
60
61
62
63 private static Logger logger = Logger.getLogger(OpenNapServerManager.class);
64
65 private OpenNapPreferences napPrefs = OpenNapPlugin.getPreferences();
66
67 private String stats;
68 private OpenNapListener listener = new OpenNapListener();
69 protected ListSupport ls = new ListSupport(this);
70
71 /***
72 * A list of {@link StatusListener} objects.
73 */
74 private LinkedList sl = new LinkedList();
75
76 /***
77 * Set to true by {@link #add(OpenNapServer,boolean)} if a
78 * temporary server has been added.
79 */
80 private boolean addedTemporary = false;
81
82 /***
83 * A list of {@link OpenNapServer} objects.
84 */
85 private List servers = new ArrayList();
86
87 /***
88 * A list of connected {@link OpenNapServer} objects.
89 *
90 * @see #setConnected(OpenNapServer, boolean)
91 */
92 private List connectedServers = new LinkedList();
93
94 /***
95 * A list of {@link OpenNapNetwork} objects.
96 */
97 private HashMap networkByName = new HashMap();
98
99 private AutoConnector connector = new AutoConnector();
100
101 /***
102 * The indes of the next repository file to share.
103 *
104 * @see PostLoginRunner.shareFiles()
105 */
106 private int repositoryIndex = 0;
107
108 private AutoFetchTask autoFetchTask;
109
110 /***
111 * Set to true once downloads have been auto resumed.
112 */
113 private boolean resumed = false;
114
115
116
117 public OpenNapServerManager()
118 {
119 OpenNapPlugin.getPreferences().addPropertyChangeListener(this);
120
121
122
123 networkByName.put("", new OpenNapDefaultNetwork());
124
125 updateStats();
126 }
127
128
129
130 /***
131 * <code>listener</code> is notified when a server is added or removed.
132 */
133 public void addListListener(ListListener listener)
134 {
135 ls.addListListener(listener);
136 }
137
138 public void removeListListener(ListListener listener)
139 {
140 ls.removeListListener(listener);
141 }
142
143 /***
144 * <code>listener</code> is notified when a server is added or removed.
145 */
146 public synchronized void addStatsListener(StatusListener listener)
147 {
148 sl.add(listener);
149 }
150
151 public synchronized void removeStatsListener(StatusListener listener)
152 {
153 sl.remove(listener);
154 }
155
156 public boolean add(String url, String network, boolean login)
157 {
158 StringTokenizer s = new StringTokenizer(url, ":");
159
160 if (s.countTokens() != 2) {
161 return false;
162 }
163
164 String ip = s.nextToken();
165 int port = 0;
166 try {
167 port = Integer.parseInt(s.nextToken());
168 }
169 catch (NumberFormatException e) {
170 }
171
172 if (ip.length() == 0
173 || port < PortRange.MIN_PORT || port > PortRange.MAX_PORT) {
174 return false;
175 }
176
177 OpenNapServer server = new OpenNapServer(ip, port, network);
178 add(server, true);
179
180
181
182
183
184 return true;
185 }
186
187 /***
188 * Adds <code>server</code> if it is not already in the list.
189 *
190 * @return true, if the server was added; false, otherwise
191 */
192 public boolean add(OpenNapServer server, boolean login)
193 {
194 synchronized (this) {
195
196 int index = 0;
197 for (int i = 0; i < servers.size(); i++) {
198 OpenNapServer next = (OpenNapServer)servers.get(i);
199 if (next.equals(server)) {
200
201 return false;
202 }
203 if (next.getLastConnect() > server.getLastConnect()) {
204 index++;
205 }
206 }
207
208 OpenNapNetwork network = getNetworkByName(server.getNetworkName());
209 server.setNetwork(network);
210 server.setListener(listener);
211
212 servers.add(index, server);
213
214 addedTemporary |= server.isTemporary();
215 server.addPropertyChangeListener(this);
216 ls.fireItemAdded(server);
217 network.add(server);
218 }
219
220 if (login) {
221 server.connect();
222 }
223
224 connector.wakeup();
225
226 return true;
227 }
228
229 /***
230 * Returns the number of servers that were added.
231 */
232 public int addFrom(OpenNapServerReader reader, boolean temporary)
233 throws IOException
234 {
235 int count = 0;
236 try {
237 reader.open();
238
239 OpenNapServer s;
240 while ((s = reader.read()) != null) {
241 s.setTemporary(temporary);
242
243 if (s.getAutoConnect()) {
244 if (add(s, true)) {
245 count++;
246 }
247 }
248 else {
249 if (add(s, false)) {
250 count++;
251 }
252 }
253 }
254
255
256 }
257 finally {
258 reader.close();
259 }
260
261 return count;
262 }
263
264 public void die()
265 {
266 setAutoFetchInterval(-1);
267
268 OpenNapPlugin.getPreferences().removePropertyChangeListener(this);
269 connector.die();
270
271 try {
272 saveTo(new OpenNapServerFileWriter(napPrefs.getServerFile()), false);
273 }
274 catch (IOException e) {
275 logger.debug("could not write file", e);
276 }
277
278 if (addedTemporary) {
279 try {
280 saveTo(new OpenNapServerFileWriter(napPrefs.getNapigatorFile()), true);
281 }
282 catch (IOException e) {
283 logger.debug("could not write file", e);
284 }
285 }
286
287 removeAll();
288 listener.die();
289 }
290
291 public void fetchServerLists()
292 {
293 Notifier.info(XNap.tr("Downloading list of OpenNap servers") + "...");
294 Runnable r = new Runnable()
295 {
296 public void run()
297 {
298 String urls
299 = OpenNapPlugin.getPreferences().getNapigatorURL();
300 StringTokenizer t = new StringTokenizer(urls, "\n");
301 while (t.hasMoreTokens()) {
302 String url = t.nextToken().trim();
303 if (!url.startsWith("#")) {
304 try {
305 addFrom(new NapigatorReader(url), true);
306 }
307 catch (IOException e) {
308 Notifier.error(XNap.tr("Could not get list of OpenNap servers: {0}", NetHelper.getErrorMessage(e)), e);
309 }
310 }
311 }
312 updateStats();
313 }
314 };
315
316 Thread t = new Thread(r, "AskNapigator");
317 t.start();
318 }
319
320 public void remove(OpenNapServer server)
321 {
322 try {
323 server.disconnect();
324 }
325 catch (IllegalOperationException e) {
326
327 }
328
329 synchronized (this) {
330 server.removePropertyChangeListener(this);
331 servers.remove(server);
332 ls.fireItemRemoved(server);
333 }
334 }
335
336 public synchronized void removeAll()
337 {
338 for (Iterator i = servers.iterator(); i.hasNext();) {
339 OpenNapServer server = (OpenNapServer)i.next();
340 try {
341 server.disconnect();
342 }
343 catch (IllegalOperationException e) {
344
345 }
346 server.removePropertyChangeListener(this);
347 }
348 servers.clear();
349 }
350
351 public synchronized OpenNapServer getServerByHost(String host)
352 {
353 for (Iterator i = servers.iterator(); i.hasNext();) {
354 OpenNapServer s = (OpenNapServer)i.next();
355 if (s.getHost().equals(host)) {
356 return s;
357 }
358 }
359 return null;
360 }
361
362 public AutoConnector getConnector()
363 {
364 return connector;
365 }
366
367 public synchronized int getConnectedCount()
368 {
369 return connectedServers.size();
370 }
371
372 public synchronized OpenNapServer[] getConnectedServers()
373 {
374 return (OpenNapServer[])connectedServers.toArray(new OpenNapServer[0]);
375 }
376
377 public OpenNapNetwork getNetworkByName(String name)
378 {
379 OpenNapNetwork n = (OpenNapNetwork)networkByName.get(name);
380 if (n == null) {
381 n = new OpenNapNetwork(name);
382 networkByName.put(name, n);
383 }
384 return n;
385 }
386
387 public synchronized OpenNapServer[] getServers()
388 {
389 return (OpenNapServer[])servers.toArray(new OpenNapServer[0]);
390 }
391
392 /***
393 * Returns the stats that were calculated during the last call to
394 * {@link #updateStats()}.
395 */
396 public String getStats()
397 {
398 return stats;
399 }
400
401 public void init()
402 {
403 updateListener();
404
405 Thread t = new Thread(new InitRunner(), "OpenNapServerManagerInit");
406 t.start();
407 }
408
409 public void changeNetwork(OpenNapServer server, String newNetworkName)
410 {
411 if (!newNetworkName.equals(server.getNetworkName())) {
412 OpenNapNetwork oldNetwork = server.getNetwork();
413 oldNetwork.remove(server);
414
415 OpenNapNetwork newNetwork = getNetworkByName(newNetworkName);
416 server.setNetwork(newNetwork);
417 newNetwork.add(server);
418 }
419 }
420
421 public int saveTo(OpenNapServerWriter writer, boolean temporary)
422 throws IOException
423 {
424 int count = 0;
425 try {
426 writer.open();
427 for (Iterator i = servers.iterator(); i.hasNext();) {
428 OpenNapServer s = (OpenNapServer)i.next();
429 if (s.isTemporary() == temporary) {
430 writer.write(s);
431 count++;
432 }
433 }
434 }
435 finally {
436 writer.close();
437 }
438
439 return count;
440 }
441
442 public int saveTo(OpenNapServerWriter writer)
443 throws IOException
444 {
445 int count = 0;
446 try {
447 writer.open();
448 for (Iterator i = servers.iterator(); i.hasNext();) {
449 OpenNapServer s = (OpenNapServer)i.next();
450 writer.write(s);
451 count++;
452 }
453 }
454 finally {
455 writer.close();
456 }
457
458 return count;
459 }
460
461 /***
462 * Sets the interval for the auto fetch task.
463 *
464 * @param interval the auto fetch interval in milli seconds; if <= 0,
465 * auto fetching is disabled */
466 public synchronized void setAutoFetchInterval(long interval)
467 {
468 if (interval <= 0) {
469 if (autoFetchTask != null) {
470 autoFetchTask.cancel();
471 autoFetchTask = null;
472 }
473 }
474 else {
475 autoFetchTask = new AutoFetchTask();
476 Scheduler.run(0, interval, autoFetchTask);
477 }
478 }
479
480 /***
481 * Invoked by {@link OpenNapServer} objects once their state
482 * changes to connected. */
483 void setConnected(OpenNapServer server, boolean connected)
484 {
485 synchronized (connectedServers) {
486 synchronized (this) {
487 if (connected) {
488 connectedServers.add(server);
489 Thread t
490 = new Thread(new PostLoginRunner(server), "PostLogin");
491 t.start();
492 }
493 else {
494 connectedServers.remove(server);
495 }
496 }
497
498 int size = connectedServers.size();
499 if (size == 1) {
500 SearchManager.getInstance().add
501 (OpenNapPlugin.getSearchManager());
502 }
503 else if (size == 0) {
504 SearchManager.getInstance().remove
505 (OpenNapPlugin.getSearchManager());
506 }
507
508 if (!resumed
509 && napPrefs.getAutoResumeDownloads()
510 && size == napPrefs.getAutoResumeConnectedCount()) {
511
512 resumed = true;
513 OpenNapPlugin.getTransferManager().startAllDownloads();
514 }
515 }
516
517 updateStats();
518 }
519
520 public void propertyChange(PropertyChangeEvent e)
521 {
522 String p = e.getPropertyName();
523
524 if (p.equals("stats")) {
525 updateStats();
526 }
527 else if (p.equals("firewalled") || p.equals("localPort")) {
528 updateListener();
529 }
530 else if (p.equals("autoFetchNapigator")
531 || p.equals("autoFetchNapigatorInterval")) {
532 updateAutoFetch();
533 }
534 }
535
536 private void updateAutoFetch()
537 {
538 setAutoFetchInterval
539 (napPrefs.getAutoFetchNapigator()
540 ? napPrefs.getAutoFetchNapigatorInterval() * 60 * 60 * 1000
541 : -1);
542 }
543
544 private void updateListener()
545 {
546 if (napPrefs.getFirewalled()) {
547 listener.setPortRange(null);
548 }
549 else {
550 PortRange range = new PortRange(napPrefs.getLocalPortRange());
551 listener.setPortRange(range);
552 if (listener.getPort() == 0) {
553 logger.info(XNap.tr("Could not start listener (check local port)"));
554 }
555 }
556 }
557
558 /***
559 * Iterates through all servers and notifies the stats listeners.
560 */
561 private synchronized void updateStats()
562 {
563 long userCount = 0;
564 long fileCount = 0;
565 long fileSize = 0;
566
567 if (connectedServers.size() > 0) {
568 for (Iterator i = connectedServers.iterator(); i.hasNext();) {
569 OpenNapServer s = (OpenNapServer)i.next();
570 userCount += s.getUserCount();
571 fileCount += s.getFileCount();
572 fileSize += s.getFileSize();
573 }
574
575
576 fileSize = fileSize * 1024 * 1024 * 1024;
577 StringBuffer sb = new StringBuffer();
578 sb.append(Formatter.formatNumber(connectedServers.size()));
579 sb.append(" " + XNap.tr("of") + " ");
580 sb.append(Formatter.formatNumber(servers.size()));
581 sb.append(" " + XNap.tr("Servers") + " / ");
582 sb.append(Formatter.formatNumber(userCount));
583 sb.append(" " + XNap.tr("Users") + " / ");
584 sb.append(Formatter.formatNumber(fileCount));
585 sb.append(" " + XNap.tr("Files") + " / ");
586 sb.append(Formatter.formatSize(fileSize));
587 sb.append(" " + XNap.tr("Shared"));
588 stats = sb.toString();
589 }
590 else {
591 stats = XNap.tr("{0} Servers (Not Connected)",
592 new Integer(servers.size()));
593 }
594
595 for (Iterator i = sl.iterator(); i.hasNext();) {
596 ((StatusListener)i.next()).setStatus(stats);
597 }
598 }
599
600 /***
601 * Spin-locks until library is read. */
602 private void waitUntilLibraryIsRead()
603 {
604 while (!Library.getInstance().isRead()) {
605 try {
606 Thread.sleep(512);
607 }
608 catch (InterruptedException e) {
609 return;
610 }
611 }
612 }
613
614
615
616 private class InitRunner implements Runnable
617 {
618
619 public void run()
620 {
621 try {
622 String filename = napPrefs.getServerFile();
623 addFrom(new OpenNapServerFileReader(filename), false);
624 }
625 catch (IOException e) {
626 logger.debug("Could not add hosts from file" , e);
627 }
628 if (napPrefs.getAutoLoadNapigator()) {
629 try {
630 String filename = napPrefs.getNapigatorFile();
631 addFrom(new OpenNapServerFileReader(filename), true);
632 }
633 catch (IOException e) {
634 logger.debug("Could not add hosts from file" , e);
635 }
636 }
637
638 updateAutoFetch();
639 }
640 }
641
642 private class ServerIterator {
643
644 private int start;
645 private boolean looped;
646 private int next = 0;
647
648 /***
649 * This needs to called prior to each cycle.
650 */
651 public void init()
652 {
653 start = next;
654 looped = false;
655 }
656
657 /***
658 * Called by AutoConnector to cycle through servers.
659 */
660 public OpenNapServer getNext()
661 {
662 synchronized (OpenNapServerManager.this) {
663 if (servers.isEmpty()) {
664 return null;
665 }
666
667 if (next > servers.size()) {
668 if (looped) {
669 return null;
670 }
671
672 if (start > servers.size() - 1) {
673 start = servers.size() - 1;
674 }
675 next = 0;
676 looped = true;
677 }
678
679 int index = next;
680
681 next++;
682 if (next >= servers.size()) {
683 next = 0;
684 looped = true;
685 }
686 if (looped && next >= start) {
687 return null;
688 }
689
690 return (OpenNapServer)servers.get(index);
691 }
692 }
693 }
694
695 public class AutoConnector implements Runnable
696 {
697
698 private boolean die = false;
699 private boolean enabled = false;
700 private Thread runner;
701 private ServerIterator iterator = new ServerIterator();
702
703 void die()
704 {
705 die = true;
706 synchronized (this) {
707 this.notify();
708 }
709 }
710
711 public synchronized boolean isEnabled()
712 {
713 return enabled;
714 }
715
716 public synchronized void setEnabled(boolean newValue)
717 {
718 enabled = newValue;
719
720 if (enabled) {
721 if (runner != null) {
722 wakeup();
723 }
724 else {
725 runner = new Thread(this, "OpenNapConnector");
726 runner.start();
727 }
728 }
729 }
730
731 public void wakeup()
732 {
733 if (isEnabled()) {
734 synchronized (this) {
735 this.notify();
736 }
737 }
738 }
739
740 /***
741 * Auto connects servers.
742 */
743 public void run()
744 {
745 waitUntilLibraryIsRead();
746
747 while (!die) {
748 int left = 2 * (napPrefs.getMaxAutoconnectServers()
749 - connectedServers.size());
750 left -= OpenNapNetwork.getTotalConnecting();
751
752 iterator.init();
753
754 OpenNapServer s;
755 while ((s = iterator.getNext()) != null && left > 0
756 && enabled) {
757 if (!s.getNetwork().isBusy() && s.isDisconnected()) {
758 long t = s.getNextAutoConnectTime();
759 if (t >= 0 && System.currentTimeMillis() >= t) {
760 s.connect();
761 left--;
762 }
763 }
764 }
765
766 synchronized (this) {
767 try {
768 if (enabled) {
769 this.wait(OpenNapServer.FAILED_INTERVAL);
770 }
771 else {
772 this.wait();
773 }
774 }
775 catch (InterruptedException e) {
776 }
777 }
778 }
779 }
780
781 }
782
783 public class PostLoginRunner implements Runnable, ExceptionListener {
784
785 OpenNapServer server;
786 boolean die;
787
788 public PostLoginRunner(OpenNapServer server)
789 {
790 this.server = server;
791 }
792
793 public void exceptionThrown(Exception e)
794 {
795 die = true;
796 }
797
798 public void run()
799 {
800 sendHotlist();
801 shareFiles();
802 }
803
804 public void sendHotlist()
805 {
806
807 OpenNapGlobalUser[] users
808 = OpenNapPlugin.getUserManager().getHotlistUsers();
809 for (int i = 0; i < users.length && !die; i++) {
810 AddHotlistEntryMessage msg
811 = new AddHotlistEntryMessage(users[i].getName());
812 msg.setExceptionListener(this);
813 MessageHandler.send(server, msg);
814 }
815 }
816
817 public void shareFiles()
818 {
819 waitUntilLibraryIsRead();
820
821 int size = Library.getInstance().size();
822 if (size == 0) {
823 return;
824 }
825
826
827 if (napPrefs.getLimitSharesPerServer()) {
828 boolean looped = false;
829 int toShare = napPrefs.getMaxSharesPerServer();
830 int start;
831 int next;
832
833 synchronized(OpenNapServerManager.this) {
834 if (repositoryIndex >= size) {
835 repositoryIndex = 0;
836 }
837 start = repositoryIndex;
838 next = start;
839 repositoryIndex += toShare;
840 }
841
842 while (toShare > 0 && !die) {
843 if (shareFile(next)) {
844 toShare--;
845 }
846 next++;
847 if (next >= size) {
848 server.setShared(new Range(start, size - 1));
849
850 next = 0;
851 looped = true;
852 }
853 if (next >= start && looped) {
854 break;
855 }
856 }
857
858 if (looped) {
859 if (next > 0) {
860 server.setShared(new Range(0, next - 1));
861 }
862 }
863 else {
864 server.setShared(new Range(start, next - 1));
865 }
866
867 synchronized(OpenNapServerManager.this) {
868 repositoryIndex = next;
869 }
870 }
871 else {
872 for (int i = 0; i < size; i++) {
873 shareFile(i);
874 }
875 server.setShared(new Range(0, size - 1));
876 }
877 }
878
879 /***
880 * Returns the number of files that have been shared.
881 *
882 * @return -1, if server disconnected
883 */
884 public boolean shareFile(int index)
885 {
886 MetaInfoFile f = Library.getInstance().get(index);
887 if (f != null && f.isShared()) {
888 MetaInfoManager.handle(f);
889
890 ShareFileMessage msg = new ShareFileMessage(index, f);
891 msg.setExceptionListener(this);
892 MessageHandler.send(server, msg);
893
894 return true;
895 }
896 return false;
897 }
898
899 }
900
901 private class AutoFetchTask extends XNapTask {
902
903 public AutoFetchTask()
904 {
905 }
906
907 public void run()
908 {
909 fetchServerLists();
910 }
911
912 }
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933 }