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.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      //--- Constant(s) ---
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      //--- Data field(s) ---
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     //--- Constructor(s) ---
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     //--- Method(s) ---
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 //  		boolean oldValue = getAutoConnect();
527 		data.autoConnect = newvalue;
528 //  		pcs.firePropertyChange("autoConnect", oldValue, 
529 //  							   autoConnect);
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     //--- Inner Class(es) ---
733 
734 	private class StateMachine extends FiniteStateMachine
735     {
736 
737 		//--- Constructor(s) ---
738 
739 		public StateMachine()
740 		{
741 			super(State.DISCONNECTED, TRANSITION_TABLE);
742 		}
743 
744 		//--- Method(s) ---
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 					// auto join channels
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 					// close all open chat channelByName
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 }