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 51KB

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