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.awt.event.ActionEvent;
23 import java.beans.PropertyChangeListener;
24 import java.beans.PropertyChangeSupport;
25 import java.util.Hashtable;
26 import java.util.Iterator;
27 import java.util.LinkedList;
28
29 import javax.swing.Action;
30
31 import org.xnap.XNap;
32 import org.xnap.chat.AbstractChatProvider;
33 import org.xnap.chat.ChannelInfo;
34 import org.xnap.chat.ChatManager;
35 import org.xnap.event.StateListener;
36 import org.xnap.event.StateSupport;
37 import org.xnap.gui.action.AbstractListChannelsAction;
38 import org.xnap.gui.action.JoinChannelAction;
39 import org.xnap.plugin.opennap.OpenNapPlugin;
40 import org.xnap.plugin.opennap.net.msg.MessageHandler;
41 import org.xnap.plugin.opennap.net.msg.client.ListChannelsMessage;
42 import org.xnap.plugin.opennap.user.OpenNapUser;
43 import org.xnap.plugin.opennap.util.OpenNapServerVersion;
44 import org.xnap.util.FiniteStateMachine;
45 import org.xnap.util.Preferences;
46 import org.xnap.util.Range;
47 import org.xnap.util.State;
48 import org.xnap.util.StringHelper;
49 import org.xnap.util.prefs.StringValidator;
50
51 public class OpenNapServer extends AbstractChatProvider {
52
53
54
55 public static final int DEFAULT_PORT = 8888;
56 public static final int DEFAULT_META_PORT = 8875;
57
58 /***
59 * Timeout before connect retry if login failed.
60 */
61 public static final int LOGIN_INTERVAL = 180 * 1000;
62
63 /***
64 * Timeout before connect retry if connection failed.
65 */
66 public static final int FAILED_INTERVAL = 60 * 1000;
67
68 private static final Hashtable TRANSITION_TABLE;
69 static {
70 State[][] table = new State[][] {
71 { State.DISCONNECTED,
72 State.CONNECTING, },
73 { State.CONNECTING,
74 State.CONNECTED, State.ERROR, State.FAILED },
75 { State.CONNECTED,
76 State.DISCONNECTING, State.FAILED },
77 { State.DISCONNECTING,
78 State.DISCONNECTED },
79 { State.ERROR,
80 State.DISCONNECTED },
81 { State.FAILED,
82 State.DISCONNECTED },
83 };
84
85 TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
86 }
87
88
89
90 private static Preferences prefs = Preferences.getInstance();
91
92 private OpenNapServerData data;
93
94 private OpenNapServerRunner runner;
95
96 private int fileCount = -1;
97 private int fileSize = -1;
98 private int userCount = -1;
99
100 private boolean newUser;
101 private String remoteIP = null;
102
103 /***
104 * Server was fetched from index service.
105 */
106 private boolean temporary = false;
107
108 private String redirectedHost;
109 private int redirectedPort;
110
111 /***
112 * The time of the next auto connect attempt.
113 */
114 private long nextAutoConnectTime = 0;
115
116 /***
117 * The listener for incoming download and direct browse request.
118 */
119 private OpenNapListener listener = null;
120
121 /***
122 * Maps nicks to {@link OpenNapUser} objects.
123 */
124 private Hashtable userByNick = new Hashtable();
125
126 /***
127 * Maps channel names to {@link OpenNapChannel} objects.
128 */
129 private Hashtable channelByName = new Hashtable();
130
131 /***
132 * The version of the software running on the remote server. Used
133 * by {@link MessageSender}.
134 */
135 private OpenNapServerVersion version = OpenNapServerVersion.UNKNOWN;
136
137 /***
138 * A list of {@link Range} objects.
139 *
140 * The range of the files from the {@link Library} that have been
141 * shared on this server.
142 */
143 private LinkedList shared = new LinkedList();
144
145 private StateMachine sm = new StateMachine();
146 private StateSupport ss = new StateSupport(this);
147 private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
148
149 private OpenNapNetwork network;
150
151
152
153 public OpenNapServer(String host, String ip, int port, String network,
154 int fileCount, int fileSize, int userCount,
155 boolean autoConnect)
156 {
157 this.data = new OpenNapServerData();
158
159 setHost(host);
160 setIP(ip);
161 setNetworkName(network);
162 setPort(port);
163 setStats(userCount, fileCount, fileSize);
164
165 if (autoConnect) {
166 this.connect();
167 }
168 }
169
170 public OpenNapServer(String host, int port, String network)
171 {
172 this(host, null, port, network, -1, -1, -1, false);
173 }
174
175 public OpenNapServer(String host, int port, String network,
176 boolean autoConnect)
177 {
178 this(host, null, port, network, -1, -1, -1, autoConnect);
179 }
180
181 public OpenNapServer(String host, int port)
182 {
183 this(host, port, "");
184 }
185
186 public OpenNapServer(String host, int port, boolean autoConnect)
187 {
188 this(host, port, "", autoConnect);
189 }
190
191 public OpenNapServer()
192 {
193 this("", DEFAULT_PORT);
194 }
195
196 public OpenNapServer(OpenNapServerData data)
197 {
198 this.data = data;
199 }
200
201
202
203 public void addPropertyChangeListener(PropertyChangeListener l)
204 {
205 pcs.addPropertyChangeListener(l);
206 }
207
208 public void removePropertyChangeListener(PropertyChangeListener l)
209 {
210 pcs.removePropertyChangeListener(l);
211 }
212
213 public void addStateListener(StateListener listener)
214 {
215 ss.addStateListener(listener);
216 }
217
218 public void removeStateListener(StateListener listener)
219 {
220 ss.removeStateListener(listener);
221 }
222
223 public boolean canAutoRemove()
224 {
225 return isTemporary() && sm.getState() == State.ERROR;
226 }
227
228 /***
229 * Connects to the server.
230 */
231 public void connect()
232 {
233 setState(State.CONNECTING);
234 }
235
236 /***
237 * Disconnects from the server.
238 */
239 public void disconnect()
240 {
241 setState(State.DISCONNECTING);
242 }
243
244 /***
245 * Adds the channel to the {@link ChatManager} and joins the channel.
246 */
247 public void join(String name)
248 {
249 OpenNapChannel c = getChannel(name, true);
250 c.join();
251 }
252
253 /***
254 * Returns true if <code>obj</code> has the same hostname and port.
255 */
256 public boolean equals(Object obj)
257 {
258 if (obj instanceof OpenNapServer) {
259 OpenNapServer s = (OpenNapServer)obj;
260 return (getHost().equalsIgnoreCase(s.getHost())
261 && getPort() == s.getPort());
262 }
263
264 return false;
265 }
266
267 public synchronized OpenNapChannel getChannel(String name, boolean add)
268 {
269 OpenNapChannel c = (OpenNapChannel)channelByName.get(name);
270 if (c == null) {
271 c = new OpenNapChannel(this, name);
272 channelByName.put(name, c);
273 }
274 if (add && !ChatManager.getInstance().contains(c)) {
275 ChatManager.getInstance().add(c);
276 }
277 return c;
278 }
279
280 public synchronized OpenNapChannel getChannel(String name)
281 {
282 return getChannel(name, false);
283 }
284
285 public Action[] getActions()
286 {
287 return new Action[] {
288 new JoinChannelAction(this), new ListChannelsAction(),
289 };
290 }
291
292 public String getAutoJoinChannels()
293 {
294 return data.autoJoinChannels;
295 }
296
297 public boolean getAutoConnect()
298 {
299 return data.autoConnect;
300 }
301
302 public ChannelInfo[] getChannels()
303 {
304 return (OpenNapChannel[])channelByName.values().toArray
305 (new OpenNapChannel[0]);
306 }
307
308 public String getEmail()
309 {
310 return (data.email != null) ? data.email : prefs.getEmail();
311 }
312
313 public int getFileCount()
314 {
315 return fileCount;
316 }
317
318 public int getFileSize()
319 {
320 return fileSize;
321 }
322
323 public String getHost()
324 {
325 return data.host;
326 }
327
328 public String getIP()
329 {
330 return remoteIP;
331 }
332
333 /***
334 * Returns the absolute time of the last successful connect.
335 */
336 public long getLastConnect()
337 {
338 return data.lastConnect;
339 }
340
341 /***
342 * Returns the associated listener for incoming requests. Needed
343 * for reverse downloads.
344 */
345 public OpenNapListener getListener()
346 {
347 return listener;
348 }
349
350 public OpenNapUser getLocalPeer()
351 {
352 return getUser(getNick());
353 }
354
355 /***
356 * Returns port of listener or 0 if there is no listener running, which
357 * means XNap is behind a firewall.
358 */
359 public int getLocalPort()
360 {
361 return (listener != null) ? listener.getPort() : 0;
362 }
363
364 /***
365 *
366 */
367 public String getName()
368 {
369 String network = getNetwork().getName();
370 if (network.length() > 0) {
371 return network;
372 }
373 else {
374 return getHost();
375 }
376 }
377
378 /***
379 * Returns the name of the network this server is part of.
380 */
381 public String getNetworkName()
382 {
383 return data.networkName;
384 }
385
386 /***
387 * Returns the network this server is part of.
388 */
389 public OpenNapNetwork getNetwork()
390 {
391 return network;
392 }
393
394 /***
395 * Returns the absolute time of the next reconnect
396 *
397 * @return -1, if the server last connect failed unrecoverably; *
398 * absolute time of next wished connect attempt in milli seconds,
399 * otherwise
400 */
401 public long getNextAutoConnectTime()
402 {
403 return nextAutoConnectTime;
404 }
405
406 public String getNick()
407 {
408 return (data.nick != null) ? data.nick : prefs.getUsername();
409 }
410
411 /***
412 * Returns the password used for the login.
413 */
414 public String getPassword()
415 {
416 return (data.password != null) ? data.password : prefs.getPassword();
417 }
418
419 /***
420 * Returns the remote port.
421 */
422 public int getPort()
423 {
424 return data.port;
425 }
426
427 public String getRedirectedHost()
428 {
429 return redirectedHost;
430 }
431
432 public int getRedirectedPort()
433 {
434 return redirectedPort;
435 }
436
437 public OpenNapServerRunner getRunner()
438 {
439 return runner;
440 }
441
442 /***
443 * Returns the index range of shared files.
444 */
445 public Range[] getShared()
446 {
447 synchronized (shared) {
448 return (Range[])shared.toArray(new Range[0]);
449 }
450 }
451
452 public void setShared(Range range)
453 {
454 synchronized (shared) {
455 shared.add(range);
456 }
457 }
458
459 public String getStatus()
460 {
461 return sm.getDescription();
462 }
463
464 public OpenNapUser getUser(String nick)
465 {
466 synchronized (userByNick) {
467 OpenNapUser u = (OpenNapUser)userByNick.get(nick);
468 if (u == null) {
469 u = new OpenNapUser(nick, this);
470 userByNick.put(nick, u);
471 u.getParent().add(this);
472 }
473 return u;
474 }
475 }
476
477 public int getUserCount()
478 {
479 return userCount;
480 }
481
482 public OpenNapServerVersion getVersion()
483 {
484 return version;
485 }
486
487 public boolean isConnected()
488 {
489 return (sm.getState() == State.CONNECTED);
490 }
491
492 public boolean isDisconnected()
493 {
494 return (sm.getState() == State.DISCONNECTED);
495 }
496
497 public boolean isLoginCustomized()
498 {
499 return (data.nick != null) || (data.password != null)
500 || (data.email != null);
501 }
502
503 public boolean isNewUser()
504 {
505 return newUser;
506 }
507
508 public boolean isRedirector()
509 {
510 return data.redirector;
511 }
512
513 public boolean isTemporary()
514 {
515 return temporary;
516 }
517
518 public synchronized void removeChannel(OpenNapChannel c)
519 {
520 channelByName.remove(c.getName());
521 ChatManager.getInstance().remove(c);
522 }
523
524 public void setAutoConnect(boolean newvalue)
525 {
526
527 data.autoConnect = newvalue;
528
529
530
531
532 }
533
534 public void setAutoJoinChannels(String autoJoinChannels)
535 {
536 String oldValue = getAutoJoinChannels();
537 data.autoJoinChannels = autoJoinChannels;
538 pcs.firePropertyChange("autoJoinChannels", oldValue,
539 autoJoinChannels);
540 }
541
542 /***
543 * @exception IllegalArgumentException if newValue is invalid.
544 */
545 public void setEmail(String email)
546 {
547 if (email != null) {
548 StringValidator.EMAIL.validate(email);
549 }
550 String oldValue = getEmail();
551 data.email = email;
552 pcs.firePropertyChange("email", oldValue, email);
553 }
554
555 public void setHost(String host)
556 {
557 String oldValue = getHost();
558 data.host = host;
559 pcs.firePropertyChange("host", oldValue, host);
560 }
561
562 /***
563 * OpenNapigator sends an ip which is sometimes more reliable than
564 * the hostname.
565 */
566 public void setIP(String newValue)
567 {
568 remoteIP = newValue;
569 }
570
571 public void setLastConnect(long lastConnect)
572 {
573 this.data.lastConnect = lastConnect;
574 }
575
576 public void setListener(OpenNapListener newValue)
577 {
578 listener = newValue;
579 }
580
581 public void setNetwork(OpenNapNetwork network)
582 {
583 OpenNapNetwork oldValue = getNetwork();
584 synchronized (sm) {
585 if (oldValue != null) {
586 oldValue.remove(this);
587 if (sm.getState() == State.CONNECTING) {
588 oldValue.setConnecting(this, false);
589 network.setConnecting(this, true);
590 }
591 else if (sm.getState() == State.CONNECTED) {
592 oldValue.setConnected(this, false);
593 network.setConnected(this, true);
594 }
595 }
596 this.network = network;
597 setNetworkName(network.getName());
598 network.setStats(this, userCount, fileCount, fileSize);
599 }
600 pcs.firePropertyChange("network", oldValue, this.network);
601 }
602
603 public void setNetworkName(String networkName)
604 {
605 data.networkName = networkName;
606 }
607
608 /***
609 * Set <code>newUser</code> to true to create a new account on the
610 * next logon.
611 */
612 public void setNewUser(boolean newUser)
613 {
614 this.newUser = newUser;
615 }
616
617 /***
618 * @exception IllegalArgumentException if <code>nick</code> is invalid.
619 */
620 public void setNick(String nick)
621 {
622 if (nick != null) {
623 StringValidator.REGULAR_STRING.validate(nick);
624 }
625 String oldValue = getNick();
626 data.nick = nick;
627 pcs.firePropertyChange("nick", oldValue, nick);
628 }
629
630 /***
631 * @exception IllegalArgumentException if <code>password</code> is invalid.
632 */
633 public void setPassword(String password)
634 {
635 if (password != null) {
636 StringValidator.REGULAR_STRING.validate(password);
637 }
638 String oldValue = getPassword();
639 data.password = password;
640 pcs.firePropertyChange("password", oldValue, password);
641 }
642
643 public void setPort(int port)
644 {
645 int oldValue = getPort();
646 data.port = port;
647 pcs.firePropertyChange("port", new Integer(oldValue),
648 new Integer(port));
649 }
650
651 public void setRedirectedHost(String redirectedHost)
652 {
653 String oldValue = getRedirectedHost();
654 this.redirectedHost = redirectedHost;
655 pcs.firePropertyChange("redirectedHost", oldValue, redirectedHost);
656 }
657
658 public void setRedirectedPort(int redirectedPort)
659 {
660 int oldValue = getRedirectedPort();
661 this.redirectedPort = redirectedPort;
662 pcs.firePropertyChange("redirectedPort", new Integer(oldValue),
663 new Integer(redirectedPort));
664 }
665
666 public void setRedirector(boolean redirector)
667 {
668 boolean oldValue = isRedirector();
669 data.redirector = redirector;
670 pcs.firePropertyChange("redirector", new Boolean(oldValue),
671 new Boolean(redirector));
672 }
673
674 public void setStats(int userCount, int fileCount, int fileSize)
675 {
676 this.userCount = userCount;
677 this.fileCount = fileCount;
678 this.fileSize = fileSize;
679 pcs.firePropertyChange("stats", null, null);
680
681 if (network != null) {
682 network.setStats(this, userCount, fileCount, fileSize);
683 }
684 }
685
686 public void setTemporary(boolean temporary)
687 {
688 boolean oldValue = isTemporary();
689 this.temporary = temporary;
690 pcs.firePropertyChange("temporary", new Boolean(oldValue),
691 new Boolean(temporary));
692 }
693
694 public void setVersion(String newValue)
695 {
696 version = new OpenNapServerVersion(newValue);
697 }
698
699 public String toString()
700 {
701 StringBuffer sb = new StringBuffer();
702 sb.append(getHost());
703 sb.append(":");
704 sb.append(getPort());
705 String network = getNetwork().getName();
706 if (network.length() > 0) {
707 sb.append(" (");
708 sb.append(network);
709 sb.append(")");
710 }
711 return sb.toString();
712 }
713
714 void setState(State newState, String description)
715 {
716 sm.setState(newState, description);
717 ss.fireStateChanged();
718 }
719
720 void setState(State newState)
721 {
722 sm.setState(newState);
723 ss.fireStateChanged();
724 }
725
726 void setStateDescription(String description)
727 {
728 sm.setDescription(description);
729 pcs.firePropertyChange("status", null, null);
730 }
731
732
733
734 private class StateMachine extends FiniteStateMachine
735 {
736
737
738
739 public StateMachine()
740 {
741 super(State.DISCONNECTED, TRANSITION_TABLE);
742 }
743
744
745
746 protected synchronized void stateChanged(State oldState,
747 State newState)
748 {
749 if (newState == State.CONNECTING) {
750 getNetwork().setConnecting(OpenNapServer.this, true);
751
752 runner = new OpenNapServerRunner(OpenNapServer.this);
753 runner.start();
754 }
755 else if (newState == State.CONNECTED) {
756 data.lastConnect = System.currentTimeMillis();
757
758 synchronized (shared) {
759 shared.clear();
760 }
761
762 getNetwork().setConnected(OpenNapServer.this, true);
763 OpenNapPlugin.getServerManager().setConnected
764 (OpenNapServer.this, true);
765 ChatManager.getInstance().add(OpenNapServer.this);
766
767 String autoJoin = OpenNapServer.this.getAutoJoinChannels();
768 if (autoJoin != null && autoJoin.length() > 0) {
769
770 String[] channels = StringHelper.toArray(autoJoin, ";");
771 for (int i = 0; i < channels.length; i++) {
772 OpenNapServer.this.join(channels[i]);
773 }
774 }
775 }
776 else if (newState == State.DISCONNECTING) {
777 runner.disconnect();
778 nextAutoConnectTime = System.currentTimeMillis();
779 }
780 else if (newState == State.ERROR) {
781 nextAutoConnectTime = -1;
782 }
783 else if (newState == State.FAILED) {
784 nextAutoConnectTime
785 = System.currentTimeMillis() + FAILED_INTERVAL;
786 }
787 else if (newState == State.DISCONNECTED) {
788 synchronized (OpenNapServer.this) {
789
790 for (Iterator i = channelByName.values().iterator();
791 i.hasNext();) {
792 ((OpenNapChannel)i.next()).setJoined
793 (false, XNap.tr("server disconnected"));
794 }
795 }
796 }
797
798 if (oldState == State.CONNECTING) {
799 getNetwork().setConnecting(OpenNapServer.this, false);
800 }
801 else if (oldState == State.CONNECTED) {
802 getNetwork().setConnected(OpenNapServer.this, false);
803 ChatManager.getInstance().remove(OpenNapServer.this);
804 OpenNapPlugin.getServerManager().setConnected
805 (OpenNapServer.this, false);
806 }
807 }
808
809 }
810
811 /***
812 * Queries the list of channels from the server.
813 */
814 private class ListChannelsAction extends AbstractListChannelsAction {
815
816 public void actionPerformed(ActionEvent event)
817 {
818 MessageHandler.send(OpenNapServer.this, new ListChannelsMessage());
819 }
820 }
821
822 }