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.pircbot;
21  
22  import java.awt.event.ActionEvent;
23  import java.beans.PropertyChangeListener;
24  import java.beans.PropertyChangeSupport;
25  import java.io.IOException;
26  import java.util.HashSet;
27  import java.util.Hashtable;
28  import java.util.Iterator;
29  
30  import javax.swing.Action;
31  
32  import org.apache.log4j.Logger;
33  import org.jibble.pircbot.DccFileTransfer;
34  import org.jibble.pircbot.NickAlreadyInUseException;
35  import org.jibble.pircbot.PircBot;
36  import org.jibble.pircbot.User;
37  import org.xnap.XNap;
38  import org.xnap.chat.AbstractChatProvider;
39  import org.xnap.chat.ChannelInfo;
40  import org.xnap.chat.ChatManager;
41  import org.xnap.chat.DefaultChannelInfo;
42  import org.xnap.cmdl.Command;
43  import org.xnap.cmdl.Console;
44  import org.xnap.cmdl.Executer;
45  import org.xnap.cmdl.LocalExecuter;
46  import org.xnap.event.StateListener;
47  import org.xnap.event.StateSupport;
48  import org.xnap.gui.action.AbstractListChannelsAction;
49  import org.xnap.gui.action.JoinChannelAction;
50  import org.xnap.net.NetHelper;
51  import org.xnap.transfer.DccDownload;
52  import org.xnap.transfer.DownloadManager;
53  import org.xnap.util.FiniteStateMachine;
54  import org.xnap.util.IllegalOperationException;
55  import org.xnap.util.Preferences;
56  import org.xnap.util.State;
57  import org.xnap.util.StringHelper;
58  
59  public class PircBotServer extends AbstractChatProvider {
60      
61      //--- Constant(s) ---
62  
63      private static final Hashtable TRANSITION_TABLE;
64      static {
65  		State[][] table = new State[][] {
66  			{ State.DISCONNECTED,  
67  			  State.CONNECTING, },
68  			{ State.CONNECTING, 
69  			  State.CONNECTED, State.DISCONNECTED, },
70  			{ State.CONNECTED, 
71  			  State.DISCONNECTED, },
72  		};
73  
74  		TRANSITION_TABLE = FiniteStateMachine.createStateTable(table);
75      }
76      
77      //--- Data field(s) ---
78  
79      private static transient Logger logger
80  		= Logger.getLogger(PircBotServer.class);
81  
82      private transient PropertyChangeSupport pcs
83  		= new PropertyChangeSupport(this);
84  
85      private PircBotServerData data;
86      private Server server;
87  
88      private StateMachine sm = new StateMachine();
89      private StateSupport listeners = new StateSupport(this);
90  
91      //--- Constructor(s) ---
92  
93      public PircBotServer(PircBotServerData data)
94      {
95  		this.data = data;
96      }
97  
98      public PircBotServer()
99      {
100 		this(new PircBotServerData());
101     }
102 
103     //--- Method(s) ---
104 
105     public void addPropertyChangeListener(PropertyChangeListener l)
106     {
107 		pcs.addPropertyChangeListener(l);
108     }
109 
110     public void removePropertyChangeListener(PropertyChangeListener l) 
111     {
112         pcs.removePropertyChangeListener(l);
113     }
114 
115     public void addStateListener(StateListener listener) 
116     {
117         listeners.addStateListener(listener);
118     }
119 
120     public void removeStateListener(StateListener listener) 
121     {
122         listeners.removeStateListener(listener);
123     }
124 
125 	/***
126 	 * Disconnects the server and removes all channels.
127 	 */
128 	public void close()
129 	{
130 		if (server != null) {
131 			server.removeAllChannels();
132 			server.disconnect();
133 		}
134 	}
135 
136     public void connect()
137     {
138 		try {
139 			setState(State.CONNECTING);
140 		}
141 		catch (IllegalOperationException e) {
142 			logger.debug("unexpected state", e);
143 		}
144     }
145 
146     public Action[] getActions()
147     {
148 		return new Action[] { new JoinChannelAction(this), 
149 							  new ListChannelsAction() };
150     }
151 
152     public ChannelInfo[] getChannels()
153     {
154 		return getBot().getChannelInfos();
155     }
156 
157     public boolean getAutoConnect()
158     {
159 		return data.autoConnect;
160     }
161 
162     public String getAutoJoinChannels()
163     {
164 		return data.autoJoinChannels;
165     }
166 
167 	public String getConnectCommand()
168 	{
169 		return data.connectCommand;
170 	}
171 
172     public String getHost()
173     {
174 		return data.host;
175     }
176 
177     public String getLogin()
178     {
179 		return data.login;
180     }
181 
182     public String getName()
183     {
184 		return data.name;
185     }
186 
187     public String getNetwork()
188     {
189 		return data.network;
190     }
191 
192     public String getNick()
193     {
194 		return data.nick;
195     }
196 	
197     /***
198      * Returns the local peer.
199      */
200     public PircBotPeer getLocalPeer()
201     {
202 		return getBot().getPeerByName(getBot().getNick());
203     }
204 
205     public String getPassword()
206     {
207 		return data.password;
208     }
209 
210     public int getPort()
211     {
212 		return data.port;
213     }
214 
215     public String getStatus()
216     {
217 		return sm.getDescription();
218     }
219 
220     public boolean isConnected()
221     {
222 		return sm.getState() == State.CONNECTED;
223     }
224 
225     public boolean isDisconnected()
226     {
227 		return sm.getState() == State.DISCONNECTED;
228     }
229 
230     public void join(String name)
231     {
232 		PircBotChannel c = getBot().getChannelByName(name);
233 		getBot().joinChannel(name);
234 		c.setJoined(true, null);
235     }
236 
237     public void setAutoConnect(boolean autoConnect)
238     {
239 		boolean oldValue = getAutoConnect();
240 		data.autoConnect = autoConnect;
241 		pcs.firePropertyChange("autoConnect", oldValue, autoConnect);
242     }
243 
244     public void setAutoJoinChannels(String autoJoinChannels)
245     {
246 		String oldValue = getAutoJoinChannels();
247 		data.autoJoinChannels = autoJoinChannels;
248 		pcs.firePropertyChange("autoJoinChannels", oldValue, 
249 							   autoJoinChannels);
250     }
251 
252 	/***
253 	 * @param string
254 	 */
255 	public void setConnectCommand(String connectCommand)
256 	{
257 		String oldValue = getConnectCommand();
258 		data.connectCommand = connectCommand;
259 		pcs.firePropertyChange("connectCommand", oldValue, 
260 							   connectCommand);
261 	}
262 
263     public void setHost(String host)
264     {
265 		String oldValue = getHost();
266 		data.host = host;
267 		pcs.firePropertyChange("host", oldValue, host);
268     }
269 
270     public void setLogin(String login)
271     {
272 		String oldValue = getLogin();
273 		data.login = login;
274 		pcs.firePropertyChange("login", oldValue, login);
275     }
276 
277     public void setName(String name)
278     {
279 		String oldValue = getName();
280 		data.name = name;
281 		pcs.firePropertyChange("name", oldValue, name);
282     }
283 
284     public void setNetwork(String network)
285     {
286 		String oldValue = getNetwork();
287 		data.network = network;
288 		pcs.firePropertyChange("network", oldValue, network);
289     }
290 
291     public void setNick(String nick)
292     {
293 		String oldValue = getNick();
294 		data.nick = nick;
295 		pcs.firePropertyChange("nick", oldValue, nick);
296     }
297 
298     public void setPassword(String password)
299     {
300 		String oldValue = getPassword();
301 		data.password = password;
302 		pcs.firePropertyChange("password", oldValue, password);
303     }
304 
305     public void setPort(int port)
306     {
307 		int oldValue = getPort();
308 		data.port = port;
309 		pcs.firePropertyChange("port", new Integer(oldValue), 
310 							   new Integer(port));
311     }
312 
313     /***
314      * Used by classes in this package to access the PircBot instance
315      * directly.
316      */
317     Server getBot()
318     {
319 		// lazy instanziation
320 		if (server == null) {
321 			server = new Server();
322 		}
323 		return server;
324     }
325 
326     /***
327      * Used by {@link PircBotPanel} for serialization.
328      */
329     PircBotServerData getData()
330     {
331 		return data;
332     }
333 
334     private void setState(State newState)
335     {
336 		sm.setState(newState);
337 		listeners.fireStateChanged();
338     }
339 
340     private void setState(State newState, String message)
341     {
342 		sm.setState(newState, message);
343 		listeners.fireStateChanged();
344     }
345 
346     //--- Inner Class(es) ---
347 
348     private class StateMachine extends FiniteStateMachine
349     {
350 
351 		//--- Constructor(s) ---
352 
353 		public StateMachine()
354 		{
355 			super(State.DISCONNECTED, TRANSITION_TABLE);
356 		}
357 
358 		//--- Method(s) ---
359 
360 		protected synchronized void stateChanged(State oldState,
361 												 State newState)
362 		{
363 			if (newState == State.CONNECTING) {
364 				Runnable runner = new Runnable() 
365 					{
366 						public void run()
367 						{
368 							connect();
369 						}
370 					};
371 				Thread t 
372 					= new Thread(runner, "PircBotServer " + getName());
373 				t.start();
374 				return;
375 			}
376 			else if (newState == State.CONNECTED) {
377 				String connectCommand = PircBotServer.this.getConnectCommand();
378 				if (connectCommand != null && connectCommand.length() > 0) {
379 					Executer.parse(connectCommand, getBot());
380 				}
381 				
382 				String autoJoin = PircBotServer.this.getAutoJoinChannels();
383 				if (autoJoin != null && autoJoin.length() > 0) {
384 					// auto join channels
385 					String[] channels = StringHelper.toArray(autoJoin, ";,");
386 					for (int i = 0; i < channels.length; i++) {
387 						PircBotServer.this.join(channels[i]);
388 					}
389 				}
390 			}
391 		}
392 
393 		private void connect() 
394 		{
395 			try {
396 				String login = PircBotServer.this.getLogin();
397 				login = (login != null && login.length() > 0) 
398 					? login 
399 					: Preferences.getInstance().getUsername();
400 
401 				String nick = PircBotServer.this.getNick();
402 				nick = (nick != null && nick.length() > 0) 
403 					? nick 
404 					: Preferences.getInstance().getUsername();
405 		
406 				String password = PircBotServer.this.getPassword();
407 
408 				for (int i = 0; i < 3; i++) {
409 					try {
410 						getBot().setLogin(login, nick);
411 						if (password != null && password.length() > 0) {
412 							getBot().connect(PircBotServer.this.getHost(),
413 											 PircBotServer.this.getPort(),
414 											 password);
415 						}
416 						else {
417 							getBot().connect(PircBotServer.this.getHost(),
418 											 PircBotServer.this.getPort());
419 						}
420 						break;
421 					}
422 					catch (NickAlreadyInUseException e2) {
423 						nick += "_";
424 					}
425 				}
426 			}
427 			catch (Exception e) {
428 				logger.debug("connect failed", e);
429 				String msg = (e instanceof IOException) 
430 					? NetHelper.getErrorMessage((IOException)e) 
431 					: e.getLocalizedMessage();
432 				PircBotServer.this.setState
433 					(State.DISCONNECTED, XNap.tr("connect failed: {0}", msg));
434 				return;
435 			}
436 		}
437 	
438     }
439 
440     class Server extends PircBot implements Console {
441 
442 		//--- Constant(s) ---
443 	
444 		//--- Data field(s) ---
445 
446 		/***
447 		 * Contains {@link ChannelInfo} objects.
448 		 */
449 		private HashSet channels = new HashSet();
450 
451 		/***
452 		 * A list of PircBotChannel objects. Contains the channels that have
453 		 * been added to the {@link ChatManager}.
454 		 */
455 		private Hashtable channelByName = new Hashtable();
456 
457 		private Hashtable peerByName = new Hashtable();
458 
459 		private LocalExecuter executer;
460 		
461 		//--- Constructor(s) ---
462 	
463 		public Server()
464 		{
465 			 executer = new LocalExecuter(getCommands());
466 		}
467 
468 		//--- Method(s) ---
469 
470 		/***
471 		 * Closes all channels.
472 		 */
473 		public void removeAllChannels()
474 		{
475 			for (Iterator i = channelByName.values().iterator(); i.hasNext();) {
476 				PircBotChannel c = (PircBotChannel)i.next();
477 				partChannel(c.getName());
478 				ChatManager.getInstance().remove(c);
479 			}
480 			channelByName.clear();
481 		}
482 
483 		public PircBotChannel getChannelByName(String name)
484 		{
485 			PircBotChannel channel = (PircBotChannel)channelByName.get(name);
486 			if (channel == null) {
487 				channel = new PircBotChannel(PircBotServer.this, name);
488 				channelByName.put(name, channel);
489 				ChatManager.getInstance().add(channel);
490 			}
491 			return channel;
492 		}
493 
494 		public PircBotChannel getPrivateChannelByName(String name)
495 		{
496 			PircBotChannel channel = (PircBotChannel)channelByName.get(name);
497 			if (channel == null) {
498 				channel = new PircBotChannel(PircBotServer.this, name);
499 				channel.add(getPeerByName(this.getNick()));
500 				channel.add(getPeerByName(name));
501 				channelByName.put(name, channel);
502 				ChatManager.getInstance().add(channel);
503 			}
504 			return channel;
505 		}
506 
507 		public void removeChannel(PircBotChannel channel)
508 		{
509 			channelByName.remove(channel.getName());
510 		}
511 
512 		public synchronized ChannelInfo[] getChannelInfos()
513 		{
514 			// add joined channels to list of channels
515 			String[] joinedChannels = this.getChannels();
516 			for (int i = 0; i < joinedChannels.length; i++) {
517 				ChannelInfo ci = new DefaultChannelInfo(joinedChannels[i]);
518 				if (!channels.contains(ci)) {
519 					channels.add(ci);
520 				}
521 			}
522 
523 			return (ChannelInfo[])channels.toArray(new ChannelInfo[0]);
524 		}
525 	
526 		public PircBotPeer getPeerByName(String name)
527 		{
528 			PircBotPeer peer = (PircBotPeer)peerByName.get(name);
529 			if (peer == null) {
530 				peer = new PircBotPeer(PircBotServer.this, name);
531 				peerByName.put(name, peer);
532 			}
533 			return peer;
534 		}
535 
536 		protected synchronized void onChannelInfo(String channel, 
537 												  int userCount, String topic)
538 		{
539 			channels.add(new DefaultChannelInfo(channel, userCount, topic));
540 			channelsUpdated();
541 		}
542 
543 		protected void onAction(String sender, String login,
544 								String hostname, String channel, 
545 								String message)
546 		{
547 			PircBotChannel c = getChannelByName(channel);
548 			c.actionMessageReceived(getPeerByName(sender), message);
549 		}
550 
551 		protected void onConnect()
552 		{
553 			PircBotServer.this.setState(State.CONNECTED);
554 			logger.debug("connected to " + PircBotServer.this.getName());
555 			ChatManager.getInstance().add(PircBotServer.this);
556 		}
557 
558 		protected void onDeop(String channel, String sourceNick, 
559 							  String sourceLogin, String sourceHostname,
560 							  String recipient) 
561 		{
562 			PircBotPeer p = getPeerByName(recipient);
563 			PircBotChannel c = getChannelByName(channel);
564 			c.setOp(p, false);
565 		}
566 
567 		protected void onDeVoice(String channel, String sourceNick, 
568 								 String sourceLogin, String sourceHostname, 
569 								 String recipient) 
570 		{
571 			
572 
573 			PircBotPeer p = getPeerByName(recipient);
574 			PircBotChannel c = getChannelByName(channel);
575 			c.setVoice(p, false);
576 		}
577 	
578 		protected void onDisconnect()
579 		{
580 			for (Iterator i = channelByName.values().iterator(); i.hasNext();) {
581 				PircBotChannel c = (PircBotChannel)i.next();
582 				c.setJoined(false, XNap.tr("client disconnected"));
583 			}
584 
585 			PircBotServer.this.setState(State.DISCONNECTED);
586 			logger.debug("disconnected from " + PircBotServer.this.getName());
587 			ChatManager.getInstance().remove(PircBotServer.this);
588 		}
589 	
590 		protected void onIncomingFileTransfer(DccFileTransfer transfer)
591 		{
592 			DccDownload d = new DccDownload
593 				(getPeerByName(transfer.getNick()), 
594 				 transfer.getFile().getName(),
595 				 transfer.getSize(), 
596 				 transfer.getHostname(), 
597 				 transfer.getPort());
598 			DownloadManager.getInstance().add(d);
599 		}
600 
601 		protected void onJoin(String channel, String sender, String login, 
602 							  String hostname) 
603 		{
604 			PircBotPeer p = getPeerByName(sender);
605 			p.setHost(hostname);
606 	    
607 			PircBotChannel c = getChannelByName(channel);
608 			c.add(p);
609 		}
610 	
611 		protected void onMessage(String channel, String sender, String login,
612 								 String hostname, String message)
613 		{
614 			PircBotChannel c = getChannelByName(channel);
615 			c.messageReceived(getPeerByName(sender), message);
616 		}
617 
618 		protected void onNickChange(String oldNick, String login,
619 									String hostname, String newNick)
620 		{
621 			PircBotPeer peer = getPeerByName(oldNick);
622 			peer.setHost(hostname);
623 			peer.setName(newNick);
624 			for (Iterator i = channelByName.values().iterator(); 
625 				 i.hasNext();) {
626 				((PircBotChannel)i.next()).peerChanged(peer);
627 			}
628 		}
629 
630 		protected void onNotice(String sourceNick, String sourceLogin, 
631 								String sourceHostname, String target, 
632 								String notice) 
633 		{
634 			logger.debug("notice: " + notice);
635 
636 			// try to recognize affected channel like [#xnap]
637 			int i = notice.indexOf("[#");
638 			int j = notice.indexOf("]", i);
639 			if (i != -1 && j != -1) {
640 				target = notice.substring(i + 1, j);
641 				logger.warn("x:" + target);
642 			}
643 
644 			PircBotChannel c = getChannelByName(target);
645 			c.infoReceived(notice);
646 		}
647 
648 		protected void onPart(String channel, String sender,
649 							  String login, String hostname)
650 		{
651 			PircBotChannel c = (PircBotChannel)channelByName.get(channel);
652 			if (c != null) {
653 				if (sender.equals(this.getNick())) {
654 					c.setJoined(false, null);
655 				}
656 
657 				c.remove(getPeerByName(sender));
658 			}
659 		}
660 
661 		protected void onPrivateMessage(String sender, String login, 
662 										String hostname, String message) 
663 		{
664 			PircBotChannel c = getPrivateChannelByName(sender);
665 			c.messageReceived(getPeerByName(sender), message);
666 		}
667 
668 		protected void onOp(String channel, String sourceNick, 
669 							String sourceLogin, String sourceHostname, 
670 							String recipient) 
671 		{
672 			PircBotPeer p = getPeerByName(recipient);
673 			PircBotChannel c = getChannelByName(channel);
674 			c.setOp(p, true);
675 		}
676 
677 		protected void onTopic(String channel, String topic, String setBy, long date, boolean changed)
678 		{
679 			PircBotChannel c = getChannelByName(channel);
680 			c.topicChanged(topic);
681 		}
682 	
683 		protected void onServerResponse(int code, String response)
684 		{
685 			logger.debug(code + ": " + response);
686 	    
687 			String nick = this.getNick();
688 			if (response.startsWith(nick + " :")) {
689 				response = response.substring(nick.length() + 2);
690 			}
691 
692 			if (code >= 400 && code <= 599) {
693 				// error message
694 				messageReceived(response + "\n");
695 				return;
696 			}
697 
698 			switch (code) {
699 			case 1:
700 			case 2:
701 			case 3:
702 			case 4:
703 			case 5:
704 			case 251:
705 			case 254:
706 			case 255:
707 			case RPL_WHOISUSER:
708 			case RPL_WHOISSERVER:
709 			case RPL_WHOISOPERATOR:
710 			case RPL_WHOWASUSER:
711 			case RPL_ENDOFWHO:
712 			case RPL_WHOISIDLE:
713 			case RPL_ENDOFWHOIS:
714 			case RPL_WHOISCHANNELS:
715 			case RPL_MOTD:
716 				messageReceived(response + "\n");
717 				break;
718 			}
719 		}
720 
721 		protected void onUserList(String channel, User[] users) 
722 		{
723 			PircBotChannel c = getChannelByName(channel);
724 			for (int i = 0; i < users.length; i++) {
725 				PircBotPeer p = getPeerByName(users[i].getNick());
726 				c.setOp(p, users[i].isOp());
727 				c.add(p);
728 			}
729 		}
730 
731 		protected void onVoice(String channel, String sourceNick, 
732 							   String sourceLogin, String sourceHostname, 
733 							   String recipient) 
734 		{
735 			PircBotPeer p = getPeerByName(recipient);
736 			PircBotChannel c = getChannelByName(channel);
737 			c.setVoice(p, true);
738 		}
739 
740 		public void setLogin(String login, String nick)
741 		{
742 			super.setFinger("XNap/PircBot");
743 			super.setLogin(login);
744 			super.setName(nick);
745 		}
746 
747 		public void whois(String nick)
748 		{
749 			sendRawLine("WHOIS " + nick);
750 		}
751 
752 		/***
753 		 *  @see org.xnap.cmdl.Console#getCommand(java.lang.String)
754 		 */
755 		public Command getCommand(String commandName)
756 		{
757 			return executer.getCommand(commandName);
758 		}
759 
760 		/***
761 		 *  @see org.xnap.cmdl.Console#getCommands()
762 		 */
763 		public Command[] getCommands()
764 		{
765 			return new Command[] { new PircBotMessageCommand(PircBotServer.this) };
766 		}
767 
768 		/***
769 		 *  @see org.xnap.cmdl.Console#isEchoing()
770 		 */
771 		public boolean isEchoing()
772 		{
773 			return false;
774 		}
775 
776 		/***
777 		 *  @see org.xnap.cmdl.Console#readln(java.lang.String)
778 		 */
779 		public String readln(String prompt)
780 		{
781 			return null;
782 		}
783 
784 		/***
785 		 *  @see org.xnap.cmdl.Console#println(java.lang.String)
786 		 */
787 		public void println(String text)
788 		{
789 			messageReceived(text);
790 		}
791 	
792     }
793 
794     /***
795      * Queries the list of channels from the server.
796      */
797     private class ListChannelsAction extends AbstractListChannelsAction
798 	{
799         public void actionPerformed(ActionEvent event) 
800 		{
801 			PircBotServer.this.getBot().listChannels();
802 			channelsUpdated();
803         }
804     }
805 
806 }