You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Server.java 48KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528
  1. /*
  2. * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc;
  23. import com.dmdirc.actions.ActionManager;
  24. import com.dmdirc.actions.CoreActionType;
  25. import com.dmdirc.actions.wrappers.AliasWrapper;
  26. import com.dmdirc.commandparser.CommandManager;
  27. import com.dmdirc.commandparser.CommandType;
  28. import com.dmdirc.config.ConfigManager;
  29. import com.dmdirc.config.Identity;
  30. import com.dmdirc.config.IdentityManager;
  31. import com.dmdirc.interfaces.AwayStateListener;
  32. import com.dmdirc.interfaces.InviteListener;
  33. import com.dmdirc.logger.ErrorLevel;
  34. import com.dmdirc.logger.Logger;
  35. import com.dmdirc.parser.irc.ChannelInfo;
  36. import com.dmdirc.parser.irc.ClientInfo;
  37. import com.dmdirc.parser.irc.IRCParser;
  38. import com.dmdirc.parser.irc.IRCStringConverter;
  39. import com.dmdirc.parser.irc.MyInfo;
  40. import com.dmdirc.parser.irc.ParserError;
  41. import com.dmdirc.parser.irc.ServerInfo;
  42. import com.dmdirc.ui.WindowManager;
  43. import com.dmdirc.ui.input.TabCompleter;
  44. import com.dmdirc.ui.input.TabCompletionType;
  45. import com.dmdirc.ui.interfaces.InputWindow;
  46. import com.dmdirc.ui.interfaces.ServerWindow;
  47. import com.dmdirc.ui.interfaces.Window;
  48. import java.io.Serializable;
  49. import java.util.ArrayList;
  50. import java.util.Hashtable;
  51. import java.util.List;
  52. import java.util.Map;
  53. import java.util.Timer;
  54. import java.util.TimerTask;
  55. import javax.net.ssl.TrustManager;
  56. /**
  57. * The Server class represents the client's view of a server. It maintains
  58. * a list of all channels, queries, etc, and handles parser callbacks pertaining
  59. * to the server.
  60. *
  61. * @author chris
  62. */
  63. public final class Server extends WritableFrameContainer implements Serializable {
  64. /**
  65. * A version number for this class. It should be changed whenever the class
  66. * structure is changed (or anything else that would prevent serialized
  67. * objects being unserialized with the new class).
  68. */
  69. private static final long serialVersionUID = 1;
  70. /** The name of the general domain. */
  71. private static final String DOMAIN_GENERAL = "general".intern();
  72. /** The name of the profile domain. */
  73. private static final String DOMAIN_PROFILE = "profile".intern();
  74. /** The name of the server domain. */
  75. private static final String DOMAIN_SERVER = "server".intern();
  76. /** Open channels that currently exist on the server. */
  77. private final Map<String, Channel> channels = new Hashtable<String, Channel>();
  78. /** Open query windows on the server. */
  79. private final List<Query> queries = new ArrayList<Query>();
  80. /** The IRC Parser instance handling this server. */
  81. private transient IRCParser parser;
  82. /** The raw frame used for this server instance. */
  83. private Raw raw;
  84. /** The ServerWindow corresponding to this server. */
  85. private ServerWindow window;
  86. /** The details of the server we're connecting to. */
  87. private ServerInfo serverInfo;
  88. /** The profile we're using. */
  89. private transient Identity profile;
  90. /** The current state of this server. */
  91. private final ServerStatus myState = new ServerStatus();
  92. /** The timer we're using to delay reconnects. */
  93. private Timer reconnectTimer;
  94. /** Channels we're meant to auto-join. */
  95. private final List<String> autochannels;
  96. /** The tabcompleter used for this server. */
  97. private final TabCompleter tabCompleter = new TabCompleter();
  98. /** The last activated internal frame for this server. */
  99. private FrameContainer activeFrame = this;
  100. /** Our reason for being away, if any. */
  101. private String awayMessage;
  102. /** Our event handler. */
  103. private final ServerEventHandler eventHandler = new ServerEventHandler(this);
  104. /** A list of outstanding invites. */
  105. private final List<Invite> invites = new ArrayList<Invite>();
  106. /** Our ignore list. */
  107. private final IgnoreList ignoreList = new IgnoreList();
  108. /** Our string convertor. */
  109. private IRCStringConverter converter = new IRCStringConverter();
  110. /** The parser factory to use. */
  111. private final ParserFactory parserFactory;
  112. /**
  113. * Creates a new instance of Server. Does not auto-join any channels, and
  114. * uses a default {@link ParserFactory}.
  115. *
  116. * @param server The hostname/ip of the server to connect to
  117. * @param port The port to connect to
  118. * @param password The server password
  119. * @param ssl Whether to use SSL or not
  120. * @param profile The profile to use
  121. */
  122. public Server(final String server, final int port, final String password,
  123. final boolean ssl, final Identity profile) {
  124. this(server, port, password, ssl, profile, new ArrayList<String>());
  125. }
  126. /**
  127. * Creates a new instance of Server. Uses a default {@link ParserFactory}.
  128. *
  129. * @param server The hostname/ip of the server to connect to
  130. * @param port The port to connect to
  131. * @param password The server password
  132. * @param ssl Whether to use SSL or not
  133. * @param profile The profile to use
  134. * @param autochannels A list of channels to auto-join when we connect
  135. */
  136. public Server(final String server, final int port, final String password,
  137. final boolean ssl, final Identity profile, final List<String> autochannels) {
  138. this(server, port, password, ssl, profile, autochannels, new ParserFactory());
  139. }
  140. /**
  141. * Creates a new instance of Server.
  142. *
  143. * @since 0.6
  144. * @param server The hostname/ip of the server to connect to
  145. * @param port The port to connect to
  146. * @param password The server password
  147. * @param ssl Whether to use SSL or not
  148. * @param profile The profile to use
  149. * @param autochannels A list of channels to auto-join when we connect
  150. * @param factory The {@link ParserFactory} to use to create parsers
  151. */
  152. public Server(final String server, final int port, final String password,
  153. final boolean ssl, final Identity profile,
  154. final List<String> autochannels, final ParserFactory factory) {
  155. super("server-disconnected", new ConfigManager("", "", server));
  156. serverInfo = new ServerInfo(server, port, password);
  157. serverInfo.setSSL(ssl);
  158. window = Main.getUI().getServer(this);
  159. ServerManager.getServerManager().registerServer(this);
  160. WindowManager.addWindow(window);
  161. window.setTitle(server + ":" + port);
  162. tabCompleter.addEntries(TabCompletionType.COMMAND,
  163. AliasWrapper.getAliasWrapper().getAliases());
  164. window.getInputHandler().setTabCompleter(tabCompleter);
  165. updateIcon();
  166. window.open();
  167. tabCompleter.addEntries(TabCompletionType.COMMAND,
  168. CommandManager.getCommandNames(CommandType.TYPE_SERVER));
  169. tabCompleter.addEntries(TabCompletionType.COMMAND,
  170. CommandManager.getCommandNames(CommandType.TYPE_GLOBAL));
  171. this.autochannels = autochannels;
  172. this.parserFactory = factory;
  173. new Timer("Server Who Timer").schedule(new TimerTask() {
  174. @Override
  175. public void run() {
  176. for (Channel channel : channels.values()) {
  177. channel.checkWho();
  178. }
  179. }
  180. }, 0, getConfigManager().getOptionInt(DOMAIN_GENERAL, "whotime", 60000));
  181. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "showrawwindow", false)) {
  182. addRaw();
  183. }
  184. connect(server, port, password, ssl, profile);
  185. }
  186. // ------------------------ CONNECTION, DISCONNECTION AND RECONNECTION -----
  187. /**
  188. * Connects to a new server with the specified details.
  189. *
  190. * @param server The hostname/ip of the server to connect to
  191. * @param port The port to connect to
  192. * @param password The server password
  193. * @param ssl Whether to use SSL or not
  194. * @param profile The profile to use
  195. */
  196. @Precondition({
  197. "The IRC Parser is null or not connected",
  198. "The specified profile is not null"
  199. })
  200. public void connect(final String server, final int port, final String password,
  201. final boolean ssl, final Identity profile) {
  202. assert profile != null;
  203. synchronized (this) {
  204. switch (myState.getState()) {
  205. case RECONNECT_WAIT:
  206. reconnectTimer.cancel();
  207. break;
  208. case CLOSING:
  209. // Ignore the connection attempt
  210. return;
  211. case CONNECTED:
  212. case CONNECTING:
  213. addLine("serverConnectInProgress");
  214. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  215. return;
  216. case DISCONNECTING:
  217. addLine("serverDisconnectInProgress");
  218. return;
  219. default:
  220. // Do nothing
  221. break;
  222. }
  223. if (parser != null) {
  224. throw new IllegalArgumentException("Connection attempt while parser "
  225. + "is still connected.\n\nMy state:" + getState());
  226. }
  227. myState.transition(ServerState.CONNECTING);
  228. ActionManager.processEvent(CoreActionType.SERVER_CONNECTING, null, this);
  229. getConfigManager().migrate("", "", server);
  230. serverInfo = new ServerInfo(server, port, password);
  231. serverInfo.setSSL(ssl);
  232. if (getConfigManager().hasOption(DOMAIN_SERVER, "proxy.address")) {
  233. serverInfo.setUseSocks(true);
  234. serverInfo.setProxyHost(getConfigManager()
  235. .getOption(DOMAIN_SERVER, "proxy.address"));
  236. serverInfo.setProxyUser(getConfigManager()
  237. .getOption(DOMAIN_SERVER, "proxy.user", ""));
  238. serverInfo.setProxyPass(getConfigManager()
  239. .getOption(DOMAIN_SERVER, "proxy.password", ""));
  240. serverInfo.setProxyPort(getConfigManager()
  241. .getOptionInt(DOMAIN_SERVER, "proxy.port", 1080));
  242. }
  243. this.profile = profile;
  244. updateIcon();
  245. addLine("serverConnecting", server, port);
  246. final MyInfo myInfo = getMyInfo();
  247. CertificateManager certManager
  248. = new CertificateManager(server, getConfigManager());
  249. parser = parserFactory.getParser(myInfo, serverInfo);
  250. parser.setTrustManager(new TrustManager[]{certManager});
  251. parser.setKeyManagers(certManager.getKeyManager());
  252. parser.setRemoveAfterCallback(true);
  253. parser.setCreateFake(true);
  254. parser.setIgnoreList(ignoreList);
  255. if (getConfigManager().hasOption(DOMAIN_GENERAL, "bindip")) {
  256. parser.setBindIP(getConfigManager().getOption(DOMAIN_GENERAL, "bindip"));
  257. }
  258. doCallbacks();
  259. awayMessage = null;
  260. removeInvites();
  261. window.setAwayIndicator(false);
  262. try {
  263. new Thread(parser, "IRC Parser thread").start();
  264. } catch (IllegalThreadStateException ex) {
  265. Logger.appError(ErrorLevel.FATAL, "Unable to start IRC Parser", ex);
  266. }
  267. }
  268. }
  269. /**
  270. * Reconnects to the IRC server with a specified reason.
  271. *
  272. * @param reason The quit reason to send
  273. */
  274. public void reconnect(final String reason) {
  275. synchronized (this) {
  276. if (myState.getState() == ServerState.CLOSING) {
  277. return;
  278. }
  279. disconnect(reason);
  280. connect(serverInfo.getHost(), serverInfo.getPort(),
  281. serverInfo.getPassword(), serverInfo.getSSL(), profile);
  282. }
  283. }
  284. /**
  285. * Reconnects to the IRC server.
  286. */
  287. public void reconnect() {
  288. reconnect(getConfigManager().getOption(DOMAIN_GENERAL, "reconnectmessage"));
  289. }
  290. /**
  291. * Disconnects from the server with the default quit message.
  292. */
  293. public void disconnect() {
  294. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  295. }
  296. /**
  297. * Disconnects from the server.
  298. *
  299. * @param reason disconnect reason
  300. */
  301. public void disconnect(final String reason) {
  302. synchronized (this) {
  303. switch (myState.getState()) {
  304. case CLOSING:
  305. case DISCONNECTING:
  306. case DISCONNECTED:
  307. case TRANSIENTLY_DISCONNECTED:
  308. return;
  309. case RECONNECT_WAIT:
  310. reconnectTimer.cancel();
  311. break;
  312. default:
  313. break;
  314. }
  315. if (parser == null) {
  316. myState.transition(ServerState.DISCONNECTED);
  317. } else {
  318. myState.transition(ServerState.DISCONNECTING);
  319. removeInvites();
  320. updateIcon();
  321. parser.disconnect(reason);
  322. }
  323. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  324. "closechannelsonquit", false)) {
  325. closeChannels();
  326. } else {
  327. clearChannels();
  328. }
  329. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  330. "closequeriesonquit", false)) {
  331. closeQueries();
  332. }
  333. }
  334. }
  335. /**
  336. * Schedules a reconnect attempt to be performed after a user-defiend delay.
  337. */
  338. @Precondition("The server state is transiently disconnected")
  339. private void doDelayedReconnect() {
  340. synchronized (this) {
  341. if (myState.getState() != ServerState.TRANSIENTLY_DISCONNECTED) {
  342. throw new IllegalStateException("doDelayedReconnect when not "
  343. + "transiently disconnected\n\nState: " + myState);
  344. }
  345. final int delay = Math.max(1000,
  346. getConfigManager().getOptionInt(DOMAIN_GENERAL, "reconnectdelay", 5000));
  347. handleNotification("connectRetry", getName(), delay / 1000);
  348. reconnectTimer = new Timer("Server Reconnect Timer");
  349. reconnectTimer.schedule(new TimerTask() {
  350. @Override
  351. public void run() {
  352. synchronized (Server.this) {
  353. if (myState.getState() == ServerState.RECONNECT_WAIT) {
  354. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  355. reconnect();
  356. }
  357. }
  358. }
  359. }, delay);
  360. myState.transition(ServerState.RECONNECT_WAIT);
  361. updateIcon();
  362. }
  363. }
  364. // ------------------------------------------------- CHILDREN HANDLING -----
  365. /**
  366. * Determines whether the server knows of the specified channel.
  367. *
  368. * @param channel The channel to be checked
  369. * @return True iff the channel is known, false otherwise
  370. */
  371. public boolean hasChannel(final String channel) {
  372. return channels.containsKey(converter.toLowerCase(channel));
  373. }
  374. /**
  375. * Retrieves the specified channel belonging to this server.
  376. *
  377. * @param channel The channel to be retrieved
  378. * @return The appropriate channel object
  379. */
  380. public Channel getChannel(final String channel) {
  381. return channels.get(converter.toLowerCase(channel));
  382. }
  383. /**
  384. * Retrieves a list of channel names belonging to this server.
  385. *
  386. * @return list of channel names belonging to this server
  387. */
  388. public List<String> getChannels() {
  389. final ArrayList<String> res = new ArrayList<String>();
  390. for (String channel : channels.keySet()) {
  391. res.add(channel);
  392. }
  393. return res;
  394. }
  395. /**
  396. * Determines whether the server knows of the specified query.
  397. *
  398. * @param host The host of the query to look for
  399. * @return True iff the query is known, false otherwise
  400. */
  401. public boolean hasQuery(final String host) {
  402. final String nick = ClientInfo.parseHost(host);
  403. for (Query query : queries) {
  404. if (converter.equalsIgnoreCase(ClientInfo.parseHost(query.getHost()), nick)) {
  405. return true;
  406. }
  407. }
  408. return false;
  409. }
  410. /**
  411. * Retrieves the specified query belonging to this server.
  412. *
  413. * @param host The host of the query to look for
  414. * @return The appropriate query object
  415. */
  416. public Query getQuery(final String host) {
  417. final String nick = ClientInfo.parseHost(host);
  418. for (Query query : queries) {
  419. if (converter.equalsIgnoreCase(ClientInfo.parseHost(query.getHost()), nick)) {
  420. return query;
  421. }
  422. }
  423. throw new IllegalArgumentException("No such query: " + host);
  424. }
  425. /**
  426. * Retrieves a list of queries belonging to this server.
  427. *
  428. * @return list of queries belonging to this server
  429. */
  430. public List<Query> getQueries() {
  431. return new ArrayList<Query>(queries);
  432. }
  433. /**
  434. * Adds a raw window to this server.
  435. */
  436. public void addRaw() {
  437. if (raw == null) {
  438. raw = new Raw(this);
  439. if (parser != null) {
  440. raw.registerCallbacks();
  441. }
  442. } else {
  443. raw.activateFrame();
  444. }
  445. }
  446. /**
  447. * Retrieves the raw window associated with this server.
  448. *
  449. * @return The raw window associated with this server.
  450. */
  451. public Raw getRaw() {
  452. return raw;
  453. }
  454. /**
  455. * Removes our reference to the raw object (presumably after it has been
  456. * closed).
  457. */
  458. public void delRaw() {
  459. raw = null; //NOPMD
  460. }
  461. /**
  462. * Removes a specific channel and window from this server.
  463. *
  464. * @param chan channel to remove
  465. */
  466. public void delChannel(final String chan) {
  467. tabCompleter.removeEntry(TabCompletionType.CHANNEL, chan);
  468. channels.remove(converter.toLowerCase(chan));
  469. }
  470. /**
  471. * Adds a specific channel and window to this server.
  472. *
  473. * @param chan channel to add
  474. */
  475. public void addChannel(final ChannelInfo chan) {
  476. synchronized (this) {
  477. if (myState.getState() == ServerState.CLOSING) {
  478. // Can't join channels while the server is closing
  479. return;
  480. }
  481. }
  482. if (hasChannel(chan.getName())) {
  483. getChannel(chan.getName()).setChannelInfo(chan);
  484. getChannel(chan.getName()).selfJoin();
  485. } else {
  486. final Channel newChan = new Channel(this, chan);
  487. tabCompleter.addEntry(TabCompletionType.CHANNEL, chan.getName());
  488. channels.put(converter.toLowerCase(chan.getName()), newChan);
  489. newChan.show();
  490. }
  491. }
  492. /**
  493. * Adds a query to this server.
  494. *
  495. * @param host host of the remote client being queried
  496. */
  497. public void addQuery(final String host) {
  498. synchronized (this) {
  499. if (myState.getState() == ServerState.CLOSING) {
  500. // Can't open queries while the server is closing
  501. return;
  502. }
  503. }
  504. if (!hasQuery(host)) {
  505. final Query newQuery = new Query(this, host);
  506. tabCompleter.addEntry(TabCompletionType.QUERY_NICK, ClientInfo.parseHost(host));
  507. queries.add(newQuery);
  508. }
  509. }
  510. /**
  511. * Deletes a query from this server.
  512. *
  513. * @param query The query that should be removed.
  514. */
  515. public void delQuery(final Query query) {
  516. tabCompleter.removeEntry(TabCompletionType.QUERY_NICK, query.getNickname());
  517. queries.remove(query);
  518. }
  519. /** {@inheritDoc} */
  520. @Override
  521. public boolean ownsFrame(final Window target) {
  522. // Check if it's our server frame
  523. if (window != null && window.equals(target)) { return true; }
  524. // Check if it's the raw frame
  525. if (raw != null && raw.ownsFrame(target)) { return true; }
  526. // Check if it's a channel frame
  527. for (Channel channel : channels.values()) {
  528. if (channel.ownsFrame(target)) { return true; }
  529. }
  530. // Check if it's a query frame
  531. for (Query query : queries) {
  532. if (query.ownsFrame(target)) { return true; }
  533. }
  534. return false;
  535. }
  536. /**
  537. * Sets the specified frame as the most-recently activated.
  538. *
  539. * @param source The frame that was activated
  540. */
  541. public void setActiveFrame(final FrameContainer source) {
  542. activeFrame = source;
  543. }
  544. /**
  545. * Retrieves a list of all children of this server instance.
  546. *
  547. * @return A list of this server's children
  548. */
  549. public List<WritableFrameContainer> getChildren() {
  550. final List<WritableFrameContainer> res = new ArrayList<WritableFrameContainer>();
  551. if (raw != null) {
  552. res.add(raw);
  553. }
  554. res.addAll(channels.values());
  555. res.addAll(queries);
  556. return res;
  557. }
  558. // --------------------------------------------- MISCELLANEOUS METHODS -----
  559. /**
  560. * Updates this server's icon.
  561. */
  562. private void updateIcon() {
  563. final String icon = myState.getState() == ServerState.CONNECTED
  564. ? serverInfo.getSSL() ? "secure-server" : "server"
  565. : "server-disconnected";
  566. setIcon(icon);
  567. }
  568. /**
  569. * Retrieves the MyInfo object used for the IRC Parser.
  570. *
  571. * @return The MyInfo object for our profile
  572. */
  573. @Precondition({
  574. "The current profile is not null",
  575. "The current profile specifies at least one nickname"
  576. })
  577. private MyInfo getMyInfo() {
  578. Logger.assertTrue(profile != null);
  579. Logger.assertTrue(!profile.getOptionList(DOMAIN_PROFILE, "nicknames").isEmpty());
  580. final MyInfo myInfo = new MyInfo();
  581. myInfo.setNickname(profile.getOptionList(DOMAIN_PROFILE, "nicknames").get(0));
  582. myInfo.setRealname(profile.getOption(DOMAIN_PROFILE, "realname"));
  583. if (profile.hasOption(DOMAIN_PROFILE, "ident")) {
  584. myInfo.setUsername(profile.getOption(DOMAIN_PROFILE, "ident"));
  585. }
  586. return myInfo;
  587. }
  588. /**
  589. * Registers callbacks.
  590. */
  591. private void doCallbacks() {
  592. if (raw != null) {
  593. raw.registerCallbacks();
  594. }
  595. eventHandler.registerCallbacks();
  596. for (Query query : queries) {
  597. query.reregister();
  598. }
  599. }
  600. /**
  601. * Joins the specified channel, or adds it to the auto-join list if the
  602. * server is not connected.
  603. *
  604. * @param channel The channel to be joined
  605. */
  606. public void join(final String channel) {
  607. synchronized (this) {
  608. if (myState.getState() == ServerState.CONNECTED) {
  609. removeInvites(channel);
  610. if (hasChannel(channel)) {
  611. getChannel(channel).join();
  612. getChannel(channel).activateFrame();
  613. } else {
  614. parser.joinChannel(channel);
  615. }
  616. } else {
  617. autochannels.add(channel);
  618. }
  619. }
  620. }
  621. /** {@inheritDoc} */
  622. @Override
  623. public void sendLine(final String line) {
  624. synchronized (this) {
  625. if (parser != null && myState.getState() == ServerState.CONNECTED) {
  626. parser.sendLine(window.getTranscoder().encode(line));
  627. }
  628. }
  629. }
  630. /** {@inheritDoc} */
  631. @Override
  632. public int getMaxLineLength() {
  633. return IRCParser.MAX_LINELENGTH;
  634. }
  635. /**
  636. * Retrieves the parser used for this connection.
  637. *
  638. * @return IRCParser this connection's parser
  639. */
  640. public IRCParser getParser() {
  641. return parser;
  642. }
  643. /**
  644. * Retrieves the profile that's in use for this server.
  645. *
  646. * @return The profile in use by this server
  647. */
  648. public Identity getProfile() {
  649. return profile;
  650. }
  651. /**
  652. * Retrieves the name of this server.
  653. *
  654. * @return The name of this server
  655. */
  656. public String getName() {
  657. return serverInfo.getHost();
  658. }
  659. /**
  660. * Retrieves the name of this server's network. The network name is
  661. * determined using the following rules:
  662. *
  663. * 1. If the server includes its network name in the 005 information, we
  664. * use that
  665. * 2. If the server's name ends in biz, com, info, net or org, we use the
  666. * second level domain (e.g., foo.com)
  667. * 3. If the server's name contains more than two dots, we drop everything
  668. * up to and including the first part, and use the remainder
  669. * 4. In all other cases, we use the full server name
  670. *
  671. * @return The name of this server's network
  672. */
  673. public String getNetwork() {
  674. if (parser == null) {
  675. throw new IllegalStateException("getNetwork called when "
  676. + "parser is null (state: " + getState() + ")");
  677. } else if (parser.getNetworkName().length() == 0) {
  678. return getNetworkFromServerName(parser.getServerName());
  679. } else {
  680. return parser.getNetworkName();
  681. }
  682. }
  683. /**
  684. * Calculates a network name from the specified server name. This method
  685. * implements parts 2-4 of the procedure documented at getNetwork().
  686. *
  687. * @param serverName The server name to parse
  688. * @return A network name for the specified server
  689. */
  690. protected static String getNetworkFromServerName(final String serverName) {
  691. final String[] parts = serverName.split("\\.");
  692. final String[] tlds = {"biz", "com", "info", "net", "org"};
  693. boolean isTLD = false;
  694. for (String tld : tlds) {
  695. if (serverName.endsWith("." + tld)) {
  696. isTLD = true;
  697. }
  698. }
  699. if (isTLD && parts.length > 2) {
  700. return parts[parts.length - 2] + "." + parts[parts.length - 1];
  701. } else if (parts.length > 2) {
  702. final StringBuilder network = new StringBuilder();
  703. for (int i = 1; i < parts.length; i++) {
  704. if (network.length() > 0) {
  705. network.append('.');
  706. }
  707. network.append(parts[i]);
  708. }
  709. return network.toString();
  710. } else {
  711. return serverName;
  712. }
  713. }
  714. /**
  715. * Retrieves the name of this server's IRCd.
  716. *
  717. * @return The name of this server's IRCd
  718. */
  719. public String getIrcd() {
  720. return parser.getIRCD(true);
  721. }
  722. /**
  723. * Returns the current away status.
  724. *
  725. * @return True if the client is marked as away, false otherwise
  726. */
  727. public boolean isAway() {
  728. return awayMessage != null;
  729. }
  730. /**
  731. * Gets the current away message.
  732. *
  733. * @return Null if the client isn't away, or a textual away message if it is
  734. */
  735. public String getAwayMessage() {
  736. return awayMessage;
  737. }
  738. /**
  739. * Returns the tab completer for this connection.
  740. *
  741. * @return The tab completer for this server
  742. */
  743. public TabCompleter getTabCompleter() {
  744. return tabCompleter;
  745. }
  746. /** {@inheritDoc} */
  747. @Override
  748. public InputWindow getFrame() {
  749. return window;
  750. }
  751. /**
  752. * Retrieves the current state for this server.
  753. *
  754. * @return This server's state
  755. */
  756. public ServerState getState() {
  757. return myState.getState();
  758. }
  759. /**
  760. * Retrieves the status object for this server. Effecting state transitions
  761. * on the object returned by this method will almost certainly cause
  762. * problems.
  763. *
  764. * @since 0.6.3
  765. * @return This server's status object.
  766. */
  767. public ServerStatus getStatus() {
  768. return myState;
  769. }
  770. /** {@inheritDoc} */
  771. @Override
  772. public void windowClosing() {
  773. synchronized (this) {
  774. // 1: Make the window non-visible
  775. window.setVisible(false);
  776. // 2: Remove any callbacks or listeners
  777. eventHandler.unregisterCallbacks();
  778. // 3: Trigger any actions neccessary
  779. if (parser != null && parser.isReady()) {
  780. disconnect();
  781. }
  782. myState.transition(ServerState.CLOSING);
  783. closeChannels();
  784. closeQueries();
  785. removeInvites();
  786. if (raw != null) {
  787. raw.close();
  788. }
  789. // 4: Trigger action for the window closing
  790. // 5: Inform any parents that the window is closing
  791. ServerManager.getServerManager().unregisterServer(this);
  792. // 6: Remove the window from the window manager
  793. WindowManager.removeWindow(window);
  794. // 7: Remove any references to the window and parents
  795. window = null; //NOPMD
  796. parser = null; //NOPMD
  797. }
  798. }
  799. /**
  800. * Closes all open channel windows associated with this server.
  801. */
  802. private void closeChannels() {
  803. for (Channel channel : new ArrayList<Channel>(channels.values())) {
  804. channel.close();
  805. }
  806. }
  807. /**
  808. * Clears the nicklist of all open channels.
  809. */
  810. private void clearChannels() {
  811. for (Channel channel : channels.values()) {
  812. channel.resetWindow();
  813. }
  814. }
  815. /**
  816. * Closes all open query windows associated with this server.
  817. */
  818. private void closeQueries() {
  819. for (Query query : new ArrayList<Query>(queries)) {
  820. query.close();
  821. }
  822. }
  823. /**
  824. * Passes the arguments to the most recently activated frame for this
  825. * server. If the frame isn't know, or isn't visible, use this frame
  826. * instead.
  827. *
  828. * @param messageType The type of message to send
  829. * @param args The arguments for the message
  830. */
  831. public void addLineToActive(final String messageType, final Object... args) {
  832. if (activeFrame == null || !activeFrame.getFrame().isVisible()) {
  833. activeFrame = this;
  834. }
  835. activeFrame.getFrame().addLine(messageType, args);
  836. }
  837. /**
  838. * Passes the arguments to all frames for this server.
  839. *
  840. * @param messageType The type of message to send
  841. * @param args The arguments of the message
  842. */
  843. public void addLineToAll(final String messageType, final Object... args) {
  844. for (Channel channel : channels.values()) {
  845. channel.getFrame().addLine(messageType, args);
  846. }
  847. for (Query query : queries) {
  848. query.getFrame().addLine(messageType, args);
  849. }
  850. addLine(messageType, args);
  851. }
  852. /**
  853. * Replies to an incoming CTCP message.
  854. *
  855. * @param source The source of the message
  856. * @param type The CTCP type
  857. * @param args The CTCP arguments
  858. */
  859. public void sendCTCPReply(final String source, final String type, final String args) {
  860. if (type.equalsIgnoreCase("VERSION")) {
  861. parser.sendCTCPReply(source, "VERSION", "DMDirc " + Main.VERSION
  862. + " - http://www.dmdirc.com/");
  863. } else if (type.equalsIgnoreCase("PING")) {
  864. parser.sendCTCPReply(source, "PING", args);
  865. } else if (type.equalsIgnoreCase("CLIENTINFO")) {
  866. parser.sendCTCPReply(source, "CLIENTINFO", "VERSION PING CLIENTINFO");
  867. }
  868. }
  869. /**
  870. * Determines if the specified channel name is valid. A channel name is
  871. * valid if we already have an existing Channel with the same name, or
  872. * we have a valid parser instance and the parser says it's valid.
  873. *
  874. * @param channelName The name of the channel to test
  875. * @return True if the channel name is valid, false otherwise
  876. */
  877. public boolean isValidChannelName(final String channelName) {
  878. return hasChannel(channelName)
  879. || (parser != null && parser.isValidChannelName(channelName));
  880. }
  881. /**
  882. * Returns this server's name.
  883. *
  884. * @return A string representation of this server (i.e., its name)
  885. */
  886. @Override
  887. public String toString() {
  888. return getName();
  889. }
  890. /**
  891. * Returns the server instance associated with this frame.
  892. *
  893. * @return the associated server connection
  894. */
  895. @Override
  896. public Server getServer() {
  897. return this;
  898. }
  899. /** {@inheritDoc} */
  900. @Override
  901. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  902. if (arg instanceof ClientInfo) {
  903. final ClientInfo clientInfo = (ClientInfo) arg;
  904. args.add(clientInfo.getNickname());
  905. args.add(clientInfo.getIdent());
  906. args.add(clientInfo.getHost());
  907. return true;
  908. } else {
  909. return super.processNotificationArg(arg, args);
  910. }
  911. }
  912. /**
  913. * Retusnt the list of invites for this server.
  914. *
  915. * @return Invite list
  916. */
  917. public List<Invite> getInvites() {
  918. return invites;
  919. }
  920. // -------------------------------------------------- PARSER CALLBACKS -----
  921. /**
  922. * Called when the server says that the nickname we're trying to use is
  923. * already in use.
  924. *
  925. * @param nickname The nickname that we were trying to use
  926. */
  927. public void onNickInUse(final String nickname) {
  928. final String lastNick = parser.getMyNickname();
  929. // If our last nick is still valid, ignore the in use message
  930. if (!converter.equalsIgnoreCase(lastNick, nickname)) {
  931. return;
  932. }
  933. String newNick = lastNick + (int) (Math.random() * 10);
  934. final List<String> alts = profile.getOptionList(DOMAIN_PROFILE, "nicknames");
  935. int offset = 0;
  936. // Loop so we can check case sensitivity
  937. for (String alt : alts) {
  938. offset++;
  939. if (converter.equalsIgnoreCase(alt, lastNick)) {
  940. break;
  941. }
  942. }
  943. if (offset < alts.size() && alts.get(offset).length() > 0) {
  944. newNick = alts.get(offset);
  945. }
  946. parser.setNickname(newNick);
  947. }
  948. /**
  949. * Called when the server sends a numeric event.
  950. *
  951. * @param numeric The numeric code for the event
  952. * @param tokens The (tokenised) arguments of the event
  953. */
  954. public void onNumeric(final int numeric, final String[] tokens) {
  955. String snumeric = String.valueOf(numeric);
  956. if (numeric < 10) {
  957. snumeric = "00" + snumeric;
  958. } else if (numeric < 100) {
  959. snumeric = "0" + snumeric;
  960. }
  961. final String withIrcd = "numeric_" + parser.getIRCD(true) + "_" + snumeric;
  962. final String sansIrcd = "numeric_" + snumeric;
  963. StringBuffer target = null;
  964. if (getConfigManager().hasOption("formatter", withIrcd)) {
  965. target = new StringBuffer(withIrcd);
  966. } else if (getConfigManager().hasOption("formatter", sansIrcd)) {
  967. target = new StringBuffer(sansIrcd);
  968. } else if (getConfigManager().hasOption("formatter", "numeric_unknown")) {
  969. target = new StringBuffer("numeric_unknown");
  970. }
  971. ActionManager.processEvent(CoreActionType.SERVER_NUMERIC, target, this,
  972. Integer.valueOf(numeric), tokens);
  973. if (target != null) {
  974. handleNotification(target.toString(), (Object[]) tokens);
  975. }
  976. }
  977. /**
  978. * Called when the socket has been closed.
  979. */
  980. public void onSocketClosed() {
  981. handleNotification("socketClosed", getName());
  982. ActionManager.processEvent(CoreActionType.SERVER_DISCONNECTED, null, this);
  983. eventHandler.unregisterCallbacks();
  984. synchronized (this) {
  985. if (myState.getState() == ServerState.CLOSING
  986. || myState.getState() == ServerState.DISCONNECTED) {
  987. // This has been triggered via .disconect()
  988. return;
  989. }
  990. if (myState.getState() == ServerState.DISCONNECTING) {
  991. myState.transition(ServerState.DISCONNECTED);
  992. } else {
  993. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  994. }
  995. parser = null;
  996. updateIcon();
  997. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  998. "closechannelsondisconnect", false)) {
  999. closeChannels();
  1000. } else {
  1001. clearChannels();
  1002. }
  1003. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  1004. "closequeriesondisconnect", false)) {
  1005. closeQueries();
  1006. }
  1007. removeInvites();
  1008. updateAwayState(null);
  1009. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  1010. "reconnectondisconnect", false)
  1011. && myState.getState() == ServerState.TRANSIENTLY_DISCONNECTED) {
  1012. doDelayedReconnect();
  1013. }
  1014. }
  1015. }
  1016. /**
  1017. * Called when an error was encountered while connecting.
  1018. *
  1019. * @param errorInfo The parser's error information
  1020. */
  1021. @Precondition("The current server state is CONNECTING")
  1022. public void onConnectError(final ParserError errorInfo) {
  1023. synchronized (this) {
  1024. if (myState.getState() == ServerState.CLOSING
  1025. || myState.getState() == ServerState.DISCONNECTING) {
  1026. // Do nothing
  1027. return;
  1028. } else if (myState.getState() != ServerState.CONNECTING) {
  1029. // Shouldn't happen
  1030. throw new IllegalStateException("Connect error when not "
  1031. + "connecting\n\n" + getStatus().getTransitionHistory());
  1032. }
  1033. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  1034. parser = null;
  1035. updateIcon();
  1036. String description;
  1037. if (errorInfo.getException() == null) {
  1038. description = errorInfo.getData();
  1039. } else {
  1040. final Exception exception = errorInfo.getException();
  1041. if (exception instanceof java.net.UnknownHostException) {
  1042. description = "Unknown host (unable to resolve)";
  1043. } else if (exception instanceof java.net.NoRouteToHostException) {
  1044. description = "No route to host";
  1045. } else if (exception instanceof java.net.SocketException
  1046. || exception instanceof javax.net.ssl.SSLException) {
  1047. description = exception.getMessage();
  1048. } else {
  1049. Logger.appError(ErrorLevel.LOW, "Unknown socket error", exception);
  1050. description = "Unknown error: " + exception.getMessage();
  1051. }
  1052. }
  1053. ActionManager.processEvent(CoreActionType.SERVER_CONNECTERROR, null,
  1054. this, description);
  1055. handleNotification("connectError", getName(), description);
  1056. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  1057. "reconnectonconnectfailure", false)) {
  1058. doDelayedReconnect();
  1059. }
  1060. }
  1061. }
  1062. /**
  1063. * Called when we fail to receive a ping reply within a set period of time.
  1064. */
  1065. public void onPingFailed() {
  1066. Main.getUI().getStatusBar().setMessage("No ping reply from "
  1067. + getName() + " for over "
  1068. + ((int) (Math.floor(parser.getPingTime(false) / 1000.0)))
  1069. + " seconds.", null, 10);
  1070. ActionManager.processEvent(CoreActionType.SERVER_NOPING, null, this,
  1071. Long.valueOf(parser.getPingTime(false)));
  1072. if (parser.getPingTime(false)
  1073. >= getConfigManager().getOptionInt(DOMAIN_SERVER, "pingtimeout", 60000)) {
  1074. handleNotification("stonedServer", getName());
  1075. reconnect();
  1076. }
  1077. }
  1078. /**
  1079. * Called after the parser receives the 005 headers from the server.
  1080. */
  1081. @Precondition("State is CONNECTING")
  1082. public void onPost005() {
  1083. synchronized (this) {
  1084. if (myState.getState() != ServerState.CONNECTING) {
  1085. // Shouldn't happen
  1086. throw new IllegalStateException("Received onPost005 while not "
  1087. + "connecting\n\nState: " + myState);
  1088. }
  1089. myState.transition(ServerState.CONNECTED);
  1090. updateIcon();
  1091. getConfigManager().migrate(parser.getIRCD(true), getNetwork(), getName());
  1092. updateIgnoreList();
  1093. converter = parser.getIRCStringConverter();
  1094. ActionManager.processEvent(CoreActionType.SERVER_CONNECTED, null, this);
  1095. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "rejoinchannels", false)) {
  1096. for (Channel chan : channels.values()) {
  1097. chan.join();
  1098. }
  1099. }
  1100. for (String channel : autochannels) {
  1101. parser.joinChannel(channel);
  1102. }
  1103. checkModeAliases();
  1104. }
  1105. }
  1106. /**
  1107. * Checks that we have the neccessary mode aliases for this server.
  1108. */
  1109. private void checkModeAliases() {
  1110. // Check we have mode aliases
  1111. final String modes = parser.getBoolChanModes() + parser.getListChanModes()
  1112. + parser.getSetOnlyChanModes() + parser.getSetUnsetChanModes();
  1113. final String umodes = parser.getUserModeString();
  1114. final StringBuffer missingModes = new StringBuffer();
  1115. final StringBuffer missingUmodes = new StringBuffer();
  1116. for (char mode : modes.toCharArray()) {
  1117. if (!getConfigManager().hasOption(DOMAIN_SERVER, "mode" + mode)) {
  1118. missingModes.append(mode);
  1119. }
  1120. }
  1121. for (char mode : umodes.toCharArray()) {
  1122. if (!getConfigManager().hasOption(DOMAIN_SERVER, "umode" + mode)) {
  1123. missingUmodes.append(mode);
  1124. }
  1125. }
  1126. if (missingModes.length() + missingUmodes.length() > 0) {
  1127. final StringBuffer missing = new StringBuffer("Missing mode aliases: ");
  1128. if (missingModes.length() > 0) {
  1129. missing.append("channel: +");
  1130. missing.append(missingModes);
  1131. }
  1132. if (missingUmodes.length() > 0) {
  1133. if (missingModes.length() > 0) {
  1134. missing.append(' ');
  1135. }
  1136. missing.append("user: +");
  1137. missing.append(missingUmodes);
  1138. }
  1139. Logger.appError(ErrorLevel.LOW, missing.toString() + " ["
  1140. + parser.getIRCD(true) + "]",
  1141. new Exception(missing.toString() + "\n" // NOPMD
  1142. + "Network: " + getNetwork() + "\n"
  1143. + "IRCd: " + parser.getIRCD(false)
  1144. + " (" + parser.getIRCD(true) + ")\n"
  1145. + "Mode alias version: "
  1146. + getConfigManager().getOption("identity", "modealiasversion", "none")
  1147. + "\n\n"));
  1148. }
  1149. }
  1150. // ---------------------------------------------- IGNORE LIST HANDLING -----
  1151. /**
  1152. * Retrieves this server's ignore list.
  1153. *
  1154. * @return This server's ignore list
  1155. */
  1156. public IgnoreList getIgnoreList() {
  1157. return ignoreList;
  1158. }
  1159. /**
  1160. * Updates this server's ignore list to use the entries stored in the
  1161. * config manager.
  1162. */
  1163. public void updateIgnoreList() {
  1164. ignoreList.clear();
  1165. ignoreList.addAll(getConfigManager().getOptionList("network", "ignorelist"));
  1166. }
  1167. /**
  1168. * Saves the contents of our ignore list to the network identity.
  1169. */
  1170. public void saveIgnoreList() {
  1171. getNetworkIdentity().setOption("network", "ignorelist", ignoreList.getRegexList());
  1172. }
  1173. // ------------------------------------------------- IDENTITY WRAPPERS -----
  1174. /**
  1175. * Retrieves the identity for this server.
  1176. *
  1177. * @return This server's identity
  1178. */
  1179. public Identity getServerIdentity() {
  1180. return IdentityManager.getServerConfig(getName());
  1181. }
  1182. /**
  1183. * Retrieves the identity for this server's network.
  1184. *
  1185. * @return This server's network identity
  1186. */
  1187. public Identity getNetworkIdentity() {
  1188. return IdentityManager.getNetworkConfig(getNetwork());
  1189. }
  1190. // --------------------------------------------------- INVITE HANDLING -----
  1191. /**
  1192. * Adds an invite listener to this server.
  1193. *
  1194. * @param listener The listener to be added
  1195. */
  1196. public void addInviteListener(final InviteListener listener) {
  1197. listeners.add(InviteListener.class, listener);
  1198. }
  1199. /**
  1200. * Removes an invite listener from this server.
  1201. *
  1202. * @param listener The listener to be removed
  1203. */
  1204. public void removeInviteListener(final InviteListener listener) {
  1205. listeners.remove(InviteListener.class, listener);
  1206. }
  1207. /**
  1208. * Adds an invite to this server, and fires the appropriate listeners.
  1209. *
  1210. * @param invite The invite to be added
  1211. */
  1212. public void addInvite(final Invite invite) {
  1213. synchronized (invites) {
  1214. for (Invite oldInvite : new ArrayList<Invite>(invites)) {
  1215. if (oldInvite.getChannel().equals(invite.getChannel())) {
  1216. removeInvite(oldInvite);
  1217. }
  1218. }
  1219. invites.add(invite);
  1220. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1221. listener.inviteReceived(this, invite);
  1222. }
  1223. }
  1224. }
  1225. /**
  1226. * Removes all invites for the specified channel.
  1227. *
  1228. * @param channel The channel to remove invites for
  1229. */
  1230. public void removeInvites(final String channel) {
  1231. for (Invite invite : new ArrayList<Invite>(invites)) {
  1232. if (invite.getChannel().equals(channel)) {
  1233. removeInvite(invite);
  1234. }
  1235. }
  1236. }
  1237. /**
  1238. * Removes all invites for all channels.
  1239. */
  1240. private void removeInvites() {
  1241. for (Invite invite : new ArrayList<Invite>(invites)) {
  1242. removeInvite(invite);
  1243. }
  1244. }
  1245. /**
  1246. * Removes an invite from this server, and fires the appropriate listeners.
  1247. *
  1248. * @param invite The invite to be removed
  1249. */
  1250. public void removeInvite(final Invite invite) {
  1251. synchronized (invites) {
  1252. invites.remove(invite);
  1253. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1254. listener.inviteExpired(this, invite);
  1255. }
  1256. }
  1257. }
  1258. // ----------------------------------------------- AWAY STATE HANDLING -----
  1259. /**
  1260. * Adds an away state lisener to this server.
  1261. *
  1262. * @param listener The listener to be added
  1263. */
  1264. public void addAwayStateListener(final AwayStateListener listener) {
  1265. listeners.add(AwayStateListener.class, listener);
  1266. }
  1267. /**
  1268. * Removes an away state lisener from this server.
  1269. *
  1270. * @param listener The listener to be removed
  1271. */
  1272. public void removeAwayStateListener(final AwayStateListener listener) {
  1273. listeners.remove(AwayStateListener.class, listener);
  1274. }
  1275. /**
  1276. * Updates our away state and fires the relevant listeners.
  1277. *
  1278. * @param message The away message to use, or null if we're not away.
  1279. */
  1280. public void updateAwayState(final String message) {
  1281. if ((awayMessage != null && awayMessage.equals(message))
  1282. || (awayMessage == null && message == null)) {
  1283. return;
  1284. }
  1285. awayMessage = message;
  1286. if (message == null) {
  1287. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1288. listener.onBack();
  1289. }
  1290. } else {
  1291. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1292. listener.onAway(message);
  1293. }
  1294. }
  1295. }
  1296. }