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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  1. /*
  2. * Copyright (c) 2006-2007 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.config.ConfigManager;
  28. import com.dmdirc.config.Identity;
  29. import com.dmdirc.interfaces.AwayStateListener;
  30. import com.dmdirc.interfaces.InviteListener;
  31. import com.dmdirc.logger.ErrorLevel;
  32. import com.dmdirc.logger.Logger;
  33. import com.dmdirc.parser.ChannelInfo;
  34. import com.dmdirc.parser.ClientInfo;
  35. import com.dmdirc.parser.IRCParser;
  36. import com.dmdirc.parser.MyInfo;
  37. import com.dmdirc.parser.ParserError;
  38. import com.dmdirc.parser.ServerInfo;
  39. import com.dmdirc.ui.WindowManager;
  40. import com.dmdirc.ui.input.TabCompleter;
  41. import com.dmdirc.ui.interfaces.InputWindow;
  42. import com.dmdirc.ui.interfaces.ServerWindow;
  43. import com.dmdirc.ui.interfaces.Window;
  44. import com.dmdirc.ui.messages.Formatter;
  45. import java.io.Serializable;
  46. import java.util.ArrayList;
  47. import java.util.Hashtable;
  48. import java.util.List;
  49. import java.util.Map;
  50. import java.util.Timer;
  51. import java.util.TimerTask;
  52. /**
  53. * The Server class represents the client's view of a server. It maintains
  54. * a list of all channels, queries, etc, and handles parser callbacks pertaining
  55. * to the server.
  56. *
  57. * @author chris
  58. */
  59. public final class Server extends WritableFrameContainer implements Serializable {
  60. /**
  61. * A version number for this class. It should be changed whenever the class
  62. * structure is changed (or anything else that would prevent serialized
  63. * objects being unserialized with the new class).
  64. */
  65. private static final long serialVersionUID = 1;
  66. /** The name of the general domain. */
  67. private static final String DOMAIN_GENERAL = "general".intern();
  68. /** The name of the profile domain. */
  69. private static final String DOMAIN_PROFILE = "profile".intern();
  70. /** The name of the server domain. */
  71. private static final String DOMAIN_SERVER = "server".intern();
  72. /** Open channels that currently exist on the server. */
  73. private final Map<String, Channel> channels = new Hashtable<String, Channel>();
  74. /** Open query windows on the server. */
  75. private final Map<String, Query> queries = new Hashtable<String, Query>();
  76. /** The IRC Parser instance handling this server. */
  77. private transient IRCParser parser;
  78. /** The raw frame used for this server instance. */
  79. private Raw raw;
  80. /** The ServerWindow corresponding to this server. */
  81. private ServerWindow window;
  82. /** The details of the server we're connecting to. */
  83. private ServerInfo serverInfo;
  84. /** The profile we're using. */
  85. private transient Identity profile;
  86. /** The current state of this server. */
  87. private ServerState myState = ServerState.DISCONNECTED;
  88. /** The timer we're using to delay reconnects. */
  89. private Timer reconnectTimer;
  90. /** Channels we're meant to auto-join. */
  91. private final List<String> autochannels;
  92. /** The tabcompleter used for this server. */
  93. private final TabCompleter tabCompleter = new TabCompleter();
  94. /** The last activated internal frame for this server. */
  95. private FrameContainer activeFrame = this;
  96. /** The config manager for this server. */
  97. private ConfigManager configManager;
  98. /** Our reason for being away, if any. */
  99. private String awayMessage = "";
  100. /** Our event handler. */
  101. private final ServerEventHandler eventHandler = new ServerEventHandler(this);
  102. /** A list of outstanding invites. */
  103. private final List<Invite> invites = new ArrayList<Invite>();
  104. /**
  105. * Creates a new instance of Server.
  106. *
  107. * @param server The hostname/ip of the server to connect to
  108. * @param port The port to connect to
  109. * @param password The server password
  110. * @param ssl Whether to use SSL or not
  111. * @param profile The profile to use
  112. */
  113. public Server(final String server, final int port, final String password,
  114. final boolean ssl, final Identity profile) {
  115. this(server, port, password, ssl, profile, new ArrayList<String>());
  116. }
  117. /**
  118. * Creates a new instance of Server.
  119. *
  120. * @param server The hostname/ip of the server to connect to
  121. * @param port The port to connect to
  122. * @param password The server password
  123. * @param ssl Whether to use SSL or not
  124. * @param profile The profile to use
  125. * @param autochannels A list of channels to auto-join when we connect
  126. */
  127. public Server(final String server, final int port, final String password,
  128. final boolean ssl, final Identity profile, final List<String> autochannels) {
  129. super();
  130. serverInfo = new ServerInfo(server, port, password);
  131. serverInfo.setSSL(ssl);
  132. ServerManager.getServerManager().registerServer(this);
  133. configManager = new ConfigManager("", "", server);
  134. window = Main.getUI().getServer(this);
  135. WindowManager.addWindow(window);
  136. window.setTitle(server + ":" + port);
  137. tabCompleter.addEntries(AliasWrapper.getAliasWrapper().getAliases());
  138. window.getInputHandler().setTabCompleter(tabCompleter);
  139. updateIcon();
  140. window.open();
  141. tabCompleter.addEntries(CommandManager.getServerCommandNames());
  142. tabCompleter.addEntries(CommandManager.getGlobalCommandNames());
  143. this.autochannels = autochannels;
  144. new Timer("Server Who Timer").scheduleAtFixedRate(new TimerTask() {
  145. @Override
  146. public void run() {
  147. for (Channel channel : channels.values()) {
  148. channel.checkWho();
  149. }
  150. }
  151. }, 0, configManager.getOptionInt(DOMAIN_GENERAL, "whotime", 60000));
  152. if (configManager.getOptionBool(DOMAIN_GENERAL, "showrawwindow", false)) {
  153. addRaw();
  154. }
  155. connect(server, port, password, ssl, profile);
  156. }
  157. // ------------------------ CONNECTION, DISCONNECTION AND RECONNECTION -----
  158. /**
  159. * Connects to a new server with the specified details.
  160. *
  161. * @param server The hostname/ip of the server to connect to
  162. * @param port The port to connect to
  163. * @param password The server password
  164. * @param ssl Whether to use SSL or not
  165. * @param profile The profile to use
  166. */
  167. @Precondition({
  168. "The IRC Parser is null or not connected",
  169. "The specified profile is not null"
  170. })
  171. public void connect(final String server, final int port, final String password,
  172. final boolean ssl, final Identity profile) {
  173. assert(profile != null);
  174. synchronized(myState) {
  175. switch (myState) {
  176. case RECONNECT_WAIT:
  177. reconnectTimer.cancel();
  178. break;
  179. case CLOSING:
  180. Logger.appError(ErrorLevel.MEDIUM,
  181. "Connect attempt while not expecting one",
  182. new UnsupportedOperationException("Current state: " + myState));
  183. return;
  184. case CONNECTED:
  185. case CONNECTING:
  186. disconnect(configManager.getOption(DOMAIN_GENERAL, "quitmessage"));
  187. break;
  188. default:
  189. // Do nothing
  190. break;
  191. }
  192. myState = ServerState.CONNECTING;
  193. }
  194. ActionManager.processEvent(CoreActionType.SERVER_CONNECTING, null, this);
  195. assert(parser == null || parser.getSocketState() != IRCParser.STATE_OPEN);
  196. serverInfo = new ServerInfo(server, port, password);
  197. serverInfo.setSSL(ssl);
  198. this.profile = profile;
  199. configManager = new ConfigManager("", "", server);
  200. updateIcon();
  201. addLine("serverConnecting", server, port);
  202. final MyInfo myInfo = getMyInfo();
  203. parser = new IRCParser(myInfo, serverInfo);
  204. parser.setRemoveAfterCallback(true);
  205. parser.setCreateFake(true);
  206. parser.setAddLastLine(true);
  207. if (configManager.hasOption(DOMAIN_GENERAL, "bindip")) {
  208. parser.setBindIP(configManager.getOption(DOMAIN_GENERAL, "bindip"));
  209. }
  210. doCallbacks();
  211. awayMessage = "";
  212. invites.clear();
  213. window.setAwayIndicator(false);
  214. try {
  215. new Thread(parser, "IRC Parser thread").start();
  216. } catch (IllegalThreadStateException ex) {
  217. Logger.appError(ErrorLevel.FATAL, "Unable to start IRC Parser", ex);
  218. }
  219. updateIgnoreList();
  220. }
  221. /**
  222. * Reconnects to the IRC server with a specified reason.
  223. *
  224. * @param reason The quit reason to send
  225. */
  226. public void reconnect(final String reason) {
  227. synchronized(myState) {
  228. if (myState == ServerState.CLOSING) {
  229. return;
  230. }
  231. }
  232. disconnect(reason);
  233. connect(serverInfo.getHost(), serverInfo.getPort(),
  234. serverInfo.getPassword(), serverInfo.getSSL(), profile);
  235. }
  236. /**
  237. * Reconnects to the IRC server.
  238. */
  239. public void reconnect() {
  240. reconnect(configManager.getOption(DOMAIN_GENERAL, "reconnectmessage"));
  241. }
  242. /**
  243. * Disconnects from the server.
  244. *
  245. * @param reason disconnect reason
  246. */
  247. public void disconnect(final String reason) {
  248. synchronized(myState) {
  249. switch (myState) {
  250. case CLOSING:
  251. case DISCONNECTED:
  252. case TRANSIENTLY_DISCONNECTED:
  253. return;
  254. case RECONNECT_WAIT:
  255. reconnectTimer.cancel();
  256. break;
  257. default:
  258. break;
  259. }
  260. myState = ServerState.DISCONNECTED;
  261. }
  262. updateIcon();
  263. if (parser != null && parser.getSocketState() == IRCParser.STATE_OPEN) {
  264. parser.disconnect(reason);
  265. if (configManager.getOptionBool(DOMAIN_GENERAL, "closechannelsonquit", false)) {
  266. closeChannels();
  267. } else {
  268. clearChannels();
  269. }
  270. if (configManager.getOptionBool(DOMAIN_GENERAL, "closequeriesonquit", false)) {
  271. closeQueries();
  272. }
  273. }
  274. }
  275. /**
  276. * Schedules a reconnect attempt to be performed after a user-defiend delay.
  277. */
  278. private void doDelayedReconnect() {
  279. final int delay = Math.max(1,
  280. configManager.getOptionInt(DOMAIN_GENERAL, "reconnectdelay", 5));
  281. handleNotification("connectRetry", getName(), delay);
  282. reconnectTimer = new Timer("Server Reconnect Timer");
  283. reconnectTimer.schedule(new TimerTask() {
  284. @Override
  285. public void run() {
  286. synchronized(myState) {
  287. if (myState == ServerState.RECONNECT_WAIT) {
  288. myState = ServerState.TRANSIENTLY_DISCONNECTED;
  289. reconnect();
  290. }
  291. }
  292. }
  293. }, delay * 1000);
  294. myState = ServerState.RECONNECT_WAIT;
  295. updateIcon();
  296. }
  297. // ------------------------------------------------- CHILDREN HANDLING -----
  298. /**
  299. * Determines whether the server knows of the specified channel.
  300. *
  301. * @param channel The channel to be checked
  302. * @return True iff the channel is known, false otherwise
  303. */
  304. public boolean hasChannel(final String channel) {
  305. return parser != null && channels.containsKey(parser.toLowerCase(channel));
  306. }
  307. /**
  308. * Retrieves the specified channel belonging to this server.
  309. *
  310. * @param channel The channel to be retrieved
  311. * @return The appropriate channel object
  312. */
  313. public Channel getChannel(final String channel) {
  314. return channels.get(parser.toLowerCase(channel));
  315. }
  316. /**
  317. * Retrieves a list of channel names belonging to this server.
  318. *
  319. * @return list of channel names belonging to this server
  320. */
  321. public List<String> getChannels() {
  322. final ArrayList<String> res = new ArrayList<String>();
  323. for (String channel : channels.keySet()) {
  324. res.add(channel);
  325. }
  326. return res;
  327. }
  328. /**
  329. * Determines whether the server knows of the specified query.
  330. *
  331. * @param query The query to be checked
  332. * @return True iff the query is known, false otherwise
  333. */
  334. public boolean hasQuery(final String query) {
  335. return queries.containsKey(parser.toLowerCase(query));
  336. }
  337. /**
  338. * Retrieves the specified query belonging to this server.
  339. *
  340. * @param query The query to be retrieved
  341. * @return The appropriate query object
  342. */
  343. public Query getQuery(final String query) {
  344. return queries.get(parser.toLowerCase(query));
  345. }
  346. /**
  347. * Retrieves a list of queries belonging to this server.
  348. *
  349. * @return list of queries belonging to this server
  350. */
  351. public List<String> getQueries() {
  352. final ArrayList<String> res = new ArrayList<String>();
  353. for (String query : queries.keySet()) {
  354. res.add(query);
  355. }
  356. return res;
  357. }
  358. /**
  359. * Adds a raw window to this server.
  360. */
  361. public void addRaw() {
  362. raw = new Raw(this);
  363. if (parser != null) {
  364. raw.registerCallbacks();
  365. }
  366. }
  367. /**
  368. * Retrieves the raw window associated with this server.
  369. *
  370. * @return The raw window associated with this server.
  371. */
  372. public Raw getRaw() {
  373. return raw;
  374. }
  375. /**
  376. * Removes our reference to the raw object (presumably after it has been
  377. * closed).
  378. */
  379. public void delRaw() {
  380. WindowManager.removeWindow(raw.getFrame());
  381. raw = null; //NOPMD
  382. }
  383. /**
  384. * Removes a specific channel and window from this server.
  385. *
  386. * @param chan channel to remove
  387. */
  388. public void delChannel(final String chan) {
  389. tabCompleter.removeEntry(chan);
  390. WindowManager.removeWindow(
  391. channels.get(parser.toLowerCase(chan)).getFrame());
  392. channels.remove(parser.toLowerCase(chan));
  393. }
  394. /**
  395. * Adds a specific channel and window to this server.
  396. *
  397. * @param chan channel to add
  398. */
  399. public void addChannel(final ChannelInfo chan) {
  400. if (hasChannel(chan.getName())) {
  401. getChannel(chan.getName()).setChannelInfo(chan);
  402. getChannel(chan.getName()).selfJoin();
  403. } else {
  404. final Channel newChan = new Channel(this, chan);
  405. tabCompleter.addEntry(chan.getName());
  406. channels.put(parser.toLowerCase(chan.getName()), newChan);
  407. newChan.show();
  408. }
  409. }
  410. /**
  411. * Adds a query to this server.
  412. *
  413. * @param host host of the remote client being queried
  414. */
  415. public void addQuery(final String host) {
  416. if (!queries.containsKey(parser.toLowerCase(ClientInfo.parseHost(host)))) {
  417. final Query newQuery = new Query(this, host);
  418. tabCompleter.addEntry(ClientInfo.parseHost(host));
  419. queries.put(parser.toLowerCase(ClientInfo.parseHost(host)), newQuery);
  420. }
  421. }
  422. /**
  423. * Deletes a query from this server.
  424. *
  425. * @param host host of the remote client being queried
  426. */
  427. public void delQuery(final String host) {
  428. tabCompleter.removeEntry(ClientInfo.parseHost(host));
  429. WindowManager.removeWindow(
  430. queries.get(parser.toLowerCase(ClientInfo.parseHost(host))).getFrame());
  431. queries.remove(parser.toLowerCase(ClientInfo.parseHost(host)));
  432. }
  433. /** {@inheritDoc} */
  434. @Override
  435. public boolean ownsFrame(final Window target) {
  436. // Check if it's our server frame
  437. if (window != null && window.equals(target)) { return true; }
  438. // Check if it's the raw frame
  439. if (raw != null && raw.ownsFrame(target)) { return true; }
  440. // Check if it's a channel frame
  441. for (Channel channel : channels.values()) {
  442. if (channel.ownsFrame(target)) { return true; }
  443. }
  444. // Check if it's a query frame
  445. for (Query query : queries.values()) {
  446. if (query.ownsFrame(target)) { return true; }
  447. }
  448. return false;
  449. }
  450. /**
  451. * Sets the specified frame as the most-recently activated.
  452. *
  453. * @param source The frame that was activated
  454. */
  455. public void setActiveFrame(final FrameContainer source) {
  456. activeFrame = source;
  457. }
  458. /**
  459. * Retrieves a list of all children of this server instance.
  460. *
  461. * @return A list of this server's children
  462. */
  463. public List<WritableFrameContainer> getChildren() {
  464. final List<WritableFrameContainer> res = new ArrayList<WritableFrameContainer>();
  465. if (raw != null) {
  466. res.add(raw);
  467. }
  468. res.addAll(channels.values());
  469. res.addAll(queries.values());
  470. return res;
  471. }
  472. // --------------------------------------------- MISCELLANEOUS METHODS -----
  473. /**
  474. * Updates this server's icon.
  475. */
  476. private void updateIcon() {
  477. icon = IconManager.getIconManager().getIcon(
  478. myState == ServerState.CONNECTED ?
  479. serverInfo.getSSL() ? "secure-server" : "server" : "server-disconnected");
  480. if (window != null) {
  481. window.setFrameIcon(icon);
  482. iconUpdated(icon);
  483. }
  484. }
  485. /**
  486. * Retrieves the MyInfo object used for the IRC Parser.
  487. *
  488. * @return The MyInfo object for our profile
  489. */
  490. @Precondition("The current profile is not null")
  491. private MyInfo getMyInfo() {
  492. assert(profile != null);
  493. final MyInfo myInfo = new MyInfo();
  494. myInfo.setNickname(profile.getOption(DOMAIN_PROFILE, "nickname"));
  495. myInfo.setRealname(profile.getOption(DOMAIN_PROFILE, "realname"));
  496. if (profile.hasOption(DOMAIN_PROFILE, "ident")) {
  497. myInfo.setUsername(profile.getOption(DOMAIN_PROFILE, "ident"));
  498. }
  499. return myInfo;
  500. }
  501. /**
  502. * Registers callbacks.
  503. */
  504. private void doCallbacks() {
  505. if (raw != null) {
  506. raw.registerCallbacks();
  507. }
  508. eventHandler.registerCallbacks();
  509. for (Query query : queries.values()) {
  510. query.reregister();
  511. }
  512. }
  513. /**
  514. * Joins the specified channel.
  515. *
  516. * @param channel The channel to be joined
  517. */
  518. @Precondition("This server is connected")
  519. public void join(final String channel) {
  520. assert(myState == ServerState.CONNECTED);
  521. if (hasChannel(channel)) {
  522. getChannel(channel).join();
  523. } else {
  524. parser.joinChannel(channel);
  525. }
  526. }
  527. /** {@inheritDoc} */
  528. @Override
  529. public void sendLine(final String line) {
  530. synchronized(myState) {
  531. if (parser != null && myState == ServerState.CONNECTED) {
  532. parser.sendLine(window.getTranscoder().encode(line));
  533. }
  534. }
  535. }
  536. /** {@inheritDoc} */
  537. @Override
  538. public int getMaxLineLength() {
  539. return IRCParser.MAX_LINELENGTH;
  540. }
  541. /**
  542. * Updates the ignore list for this server.
  543. */
  544. public void updateIgnoreList() {
  545. if (parser == null || parser.getIgnoreList() == null) {
  546. return;
  547. }
  548. parser.getIgnoreList().clear();
  549. if (configManager.hasOption("network", "ignorelist")) {
  550. for (String line : configManager.getOptionList("network", "ignorelist")) {
  551. parser.getIgnoreList().add(line);
  552. }
  553. }
  554. }
  555. /**
  556. * Retrieves the parser used for this connection.
  557. *
  558. * @return IRCParser this connection's parser
  559. */
  560. public IRCParser getParser() {
  561. return parser;
  562. }
  563. /**
  564. * Retrieves the profile that's in use for this server.
  565. *
  566. * @return The profile in use by this server
  567. */
  568. public Identity getProfile() {
  569. return profile;
  570. }
  571. /**
  572. * Retrieves the name of this server.
  573. *
  574. * @return The name of this server
  575. */
  576. public String getName() {
  577. return serverInfo.getHost();
  578. }
  579. /**
  580. * Retrieves the name of this server's network. The network name is
  581. * determined using the following rules:
  582. *
  583. * 1. If the server includes its network name in the 005 information, we
  584. * use that
  585. * 2. If the server's name ends in biz, com, info, net or org, we use the
  586. * second level domain (e.g., foo.com)
  587. * 3. If the server's name contains more than two dots, we drop everything
  588. * up to and including the first part, and use the remainder
  589. * 4. In all other cases, we use the full server name
  590. *
  591. * @return The name of this server's network
  592. */
  593. public String getNetwork() {
  594. if (parser == null) {
  595. return "";
  596. } else if (parser.getNetworkName().isEmpty()) {
  597. return getNetworkFromServerName(parser.getServerName());
  598. } else {
  599. return parser.getNetworkName();
  600. }
  601. }
  602. /**
  603. * Caclaultes a network name from the specified server name. This method
  604. * implements parts 2-4 of the procedure documented at getNetwork().
  605. *
  606. * @param serverName The server name to parse
  607. * @return A network name for the specified server
  608. */
  609. protected static String getNetworkFromServerName(final String serverName) {
  610. final String[] parts = serverName.split("\\.");
  611. final String[] tlds = {"biz", "com", "info", "net", "org"};
  612. boolean isTLD = false;
  613. for (String tld : tlds) {
  614. if (serverName.endsWith("." + tld)) {
  615. isTLD = true;
  616. }
  617. }
  618. if (isTLD && parts.length > 2) {
  619. return parts[parts.length - 2] + "." + parts[parts.length - 1];
  620. } else if (parts.length > 2) {
  621. final StringBuilder network = new StringBuilder();
  622. for (int i = 1; i < parts.length; i++) {
  623. if (network.length() > 0) {
  624. network.append('.');
  625. }
  626. network.append(parts[i]);
  627. }
  628. return network.toString();
  629. } else {
  630. return serverName;
  631. }
  632. }
  633. /**
  634. * Retrieves the name of this server's IRCd.
  635. *
  636. * @return The name of this server's IRCd
  637. */
  638. public String getIrcd() {
  639. return parser.getIRCD(true);
  640. }
  641. /**
  642. * Returns the current away status.
  643. *
  644. * @return True if the client is marked as away, false otherwise
  645. */
  646. public boolean isAway() {
  647. return !awayMessage.isEmpty();
  648. }
  649. /**
  650. * Gets the current away message.
  651. *
  652. * @return Null if the client isn't away, or a textual away message if it is
  653. */
  654. public String getAwayMessage() {
  655. return awayMessage;
  656. }
  657. /**
  658. * Returns the tab completer for this connection.
  659. *
  660. * @return The tab completer for this server
  661. */
  662. public TabCompleter getTabCompleter() {
  663. return tabCompleter;
  664. }
  665. /** {@inheritDoc} */
  666. @Override
  667. public InputWindow getFrame() {
  668. return window;
  669. }
  670. /** {@inheritDoc} */
  671. @Override
  672. public ConfigManager getConfigManager() {
  673. return configManager;
  674. }
  675. /**
  676. * Retrieves the current state for this server.
  677. *
  678. * @return This server's state
  679. */
  680. public ServerState getState() {
  681. return myState;
  682. }
  683. /**
  684. * Closes this server connection and associated windows.
  685. *
  686. * @param reason reason for closing
  687. */
  688. public void close(final String reason) {
  689. if (parser != null) {
  690. // Unregister parser callbacks
  691. parser.getCallbackManager().delAllCallback(eventHandler);
  692. }
  693. // Disconnect from the server
  694. disconnect(reason);
  695. myState = ServerState.CLOSING;
  696. // Close all channel windows
  697. closeChannels();
  698. // Close all query windows
  699. closeQueries();
  700. // Close the raw window
  701. if (raw != null) {
  702. raw.close();
  703. }
  704. // Unregister ourselves with the server manager
  705. ServerManager.getServerManager().unregisterServer(this);
  706. if (window != null) {
  707. window.setVisible(false);
  708. Main.getUI().getMainWindow().delChild(window);
  709. window = null; //NOPMD
  710. }
  711. // Ditch the parser
  712. parser = null; //NOPMD
  713. }
  714. /** {@inheritDoc} */
  715. @Override
  716. public void close() {
  717. close(configManager.getOption(DOMAIN_GENERAL, "quitmessage"));
  718. }
  719. /**
  720. * Closes all open channel windows associated with this server.
  721. */
  722. private void closeChannels() {
  723. for (Channel channel : channels.values()) {
  724. WindowManager.removeWindow(channel.getFrame());
  725. channel.closeWindow(false);
  726. }
  727. channels.clear();
  728. }
  729. /**
  730. * Clears the nicklist of all open channels.
  731. */
  732. private void clearChannels() {
  733. for (Channel channel : channels.values()) {
  734. channel.resetWindow();
  735. }
  736. }
  737. /**
  738. * Closes all open query windows associated with this server.
  739. */
  740. private void closeQueries() {
  741. for (Query query : queries.values()) {
  742. WindowManager.removeWindow(query.getFrame());
  743. query.close(false);
  744. }
  745. queries.clear();
  746. }
  747. /**
  748. * Passes the arguments to the most recently activated frame for this
  749. * server. If the frame isn't know, or isn't visible, use this frame
  750. * instead.
  751. *
  752. * @param messageType The type of message to send
  753. * @param args The arguments for the message
  754. */
  755. public void addLineToActive(final String messageType, final Object... args) {
  756. if (activeFrame == null || !activeFrame.getFrame().isVisible()) {
  757. activeFrame = this;
  758. }
  759. activeFrame.getFrame().addLine(messageType, args);
  760. }
  761. /**
  762. * Passes the arguments to all frames for this server.
  763. *
  764. * @param messageType The type of message to send
  765. * @param args The arguments of the message
  766. */
  767. public void addLineToAll(final String messageType, final Object... args) {
  768. for (Channel channel : channels.values()) {
  769. channel.getFrame().addLine(messageType, args);
  770. }
  771. for (Query query : queries.values()) {
  772. query.getFrame().addLine(messageType, args);
  773. }
  774. addLine(messageType, args);
  775. }
  776. /**
  777. * Replies to an incoming CTCP message.
  778. *
  779. * @param source The source of the message
  780. * @param type The CTCP type
  781. * @param args The CTCP arguments
  782. */
  783. public void sendCTCPReply(final String source, final String type, final String args) {
  784. if (type.equalsIgnoreCase("VERSION")) {
  785. parser.sendCTCPReply(source, "VERSION", "DMDirc " + Main.VERSION
  786. + " - http://www.dmdirc.com/");
  787. } else if (type.equalsIgnoreCase("PING")) {
  788. parser.sendCTCPReply(source, "PING", args);
  789. } else if (type.equalsIgnoreCase("CLIENTINFO")) {
  790. parser.sendCTCPReply(source, "CLIENTINFO", "VERSION PING CLIENTINFO");
  791. }
  792. }
  793. /**
  794. * Determines if the specified channel name is valid. A channel name is
  795. * valid if we already have an existing Channel with the same name, or
  796. * we have a valid parser instance and the parser says it's valid.
  797. *
  798. * @param channelName The name of the channel to test
  799. * @return True if the channel name is valid, false otherwise
  800. */
  801. public boolean isValidChannelName(String channelName) {
  802. return hasChannel(channelName) ||
  803. (parser != null && parser.isValidChannelName(channelName));
  804. }
  805. /**
  806. * Returns this server's name.
  807. *
  808. * @return A string representation of this server (i.e., its name)
  809. */
  810. @Override
  811. public String toString() {
  812. return getName();
  813. }
  814. /**
  815. * Returns the server instance associated with this frame.
  816. *
  817. * @return the associated server connection
  818. */
  819. @Override
  820. public Server getServer() {
  821. return this;
  822. }
  823. /** {@inheritDoc} */
  824. @Override
  825. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  826. if (arg instanceof ClientInfo) {
  827. final ClientInfo clientInfo = (ClientInfo) arg;
  828. args.add(clientInfo.getNickname());
  829. args.add(clientInfo.getIdent());
  830. args.add(clientInfo.getHost());
  831. return true;
  832. } else {
  833. return super.processNotificationArg(arg, args);
  834. }
  835. }
  836. // -------------------------------------------------- PARSER CALLBACKS -----
  837. /**
  838. * Called when the server says that the nickname we're trying to use is
  839. * already in use.
  840. *
  841. * @param nickname The nickname that we were trying to use
  842. */
  843. public void onNickInUse(final String nickname) {
  844. final String lastNick = parser.getMyNickname();
  845. // If our last nick is still valid, ignore the in use message
  846. if (!parser.equalsIgnoreCase(lastNick, nickname)) {
  847. return;
  848. }
  849. String newNick = lastNick + (int) (Math.random() * 10);
  850. if (profile.hasOption(DOMAIN_PROFILE, "altnicks")) {
  851. final String[] alts = profile.getOption(DOMAIN_PROFILE, "altnicks").split("\n");
  852. int offset = 0;
  853. if (!parser.equalsIgnoreCase(lastNick,
  854. profile.getOption(DOMAIN_PROFILE, "nickname"))) {
  855. for (String alt : alts) {
  856. offset++;
  857. if (parser.equalsIgnoreCase(alt, lastNick)) {
  858. break;
  859. }
  860. }
  861. }
  862. if (offset < alts.length && !alts[offset].isEmpty()) {
  863. newNick = alts[offset];
  864. }
  865. }
  866. parser.setNickname(newNick);
  867. }
  868. /**
  869. * Called when the server sends a numeric event.
  870. *
  871. * @param numeric The numeric code for the event
  872. * @param tokens The (tokenised) arguments of the event
  873. */
  874. public void onNumeric(final int numeric, final String[] tokens) {
  875. String snumeric = String.valueOf(numeric);
  876. if (numeric < 10) {
  877. snumeric = "00" + snumeric;
  878. } else if (numeric < 100) {
  879. snumeric = "0" + snumeric;
  880. }
  881. final String withIrcd = "numeric_" + parser.getIRCD(true) + "_" + snumeric;
  882. final String sansIrcd = "numeric_" + snumeric;
  883. String target = null;
  884. if (Formatter.hasFormat(withIrcd)) {
  885. target = withIrcd;
  886. } else if (Formatter.hasFormat(sansIrcd)) {
  887. target = sansIrcd;
  888. } else if (Formatter.hasFormat("numeric_unknown")) {
  889. target = "numeric_unknown";
  890. }
  891. if (target != null) {
  892. handleNotification(target, (Object[]) tokens);
  893. }
  894. ActionManager.processEvent(CoreActionType.SERVER_NUMERIC, null, this,
  895. Integer.valueOf(numeric), tokens);
  896. }
  897. /**
  898. * Called when the socket has been closed.
  899. */
  900. public void onSocketClosed() {
  901. handleNotification("socketClosed", getName());
  902. synchronized(myState) {
  903. if (myState == ServerState.CLOSING || myState == ServerState.DISCONNECTED) {
  904. // This has been triggered via .disconect()
  905. return;
  906. }
  907. myState = ServerState.TRANSIENTLY_DISCONNECTED;
  908. }
  909. updateIcon();
  910. if (configManager.getOptionBool(DOMAIN_GENERAL, "closechannelsondisconnect", false)) {
  911. closeChannels();
  912. } else {
  913. clearChannels();
  914. }
  915. if (configManager.getOptionBool(DOMAIN_GENERAL, "closequeriesondisconnect", false)) {
  916. closeQueries();
  917. }
  918. if (configManager.getOptionBool(DOMAIN_GENERAL, "reconnectondisconnect", false)) {
  919. doDelayedReconnect();
  920. }
  921. }
  922. /**
  923. * Called when an error was encountered while connecting.
  924. *
  925. * @param errorInfo The parser's error information
  926. */
  927. @Precondition("The current server state is CONNECTING")
  928. public void onConnectError(final ParserError errorInfo) {
  929. synchronized(myState) {
  930. assert(myState == ServerState.CONNECTING);
  931. myState = ServerState.TRANSIENTLY_DISCONNECTED;
  932. }
  933. updateIcon();
  934. String description;
  935. if (errorInfo.getException() == null) {
  936. description = errorInfo.getData();
  937. } else {
  938. final Exception exception = errorInfo.getException();
  939. if (exception instanceof java.net.UnknownHostException) {
  940. description = "Unknown host (unable to resolve)";
  941. } else if (exception instanceof java.net.NoRouteToHostException) {
  942. description = "No route to host";
  943. } else if (exception instanceof java.net.SocketException) {
  944. description = exception.getMessage();
  945. } else {
  946. Logger.appError(ErrorLevel.LOW, "Unknown socket error", exception);
  947. description = "Unknown error: " + exception.getMessage();
  948. }
  949. }
  950. ActionManager.processEvent(CoreActionType.SERVER_CONNECTERROR, null,
  951. this, description);
  952. handleNotification("connectError", getName(), description);
  953. if (configManager.getOptionBool(DOMAIN_GENERAL, "reconnectonconnectfailure", false)) {
  954. doDelayedReconnect();
  955. }
  956. }
  957. /**
  958. * Called when we fail to receive a ping reply within a set period of time.
  959. */
  960. public void onPingFailed() {
  961. Main.getUI().getStatusBar().setMessage("No ping reply from "
  962. + getName() + " for over "
  963. + ((int) (Math.floor(parser.getPingTime(false) / 1000.0)))
  964. + " seconds.", null, 10);
  965. ActionManager.processEvent(CoreActionType.SERVER_NOPING, null, this,
  966. Long.valueOf(parser.getPingTime(false)));
  967. if (parser.getPingTime(false) >=
  968. configManager.getOptionInt(DOMAIN_SERVER, "pingtimeout", 60000)) {
  969. handleNotification("stonedServer", getName());
  970. reconnect();
  971. }
  972. }
  973. /**
  974. * Called after the parser receives the 005 headers from the server.
  975. */
  976. public void onPost005() {
  977. synchronized(myState) {
  978. myState = ServerState.CONNECTED;
  979. }
  980. updateIcon();
  981. configManager = new ConfigManager(parser.getIRCD(true), getNetwork(), getName());
  982. updateIgnoreList();
  983. ActionManager.processEvent(CoreActionType.SERVER_CONNECTED, null, this);
  984. if (configManager.hasOption(DOMAIN_GENERAL, "rejoinchannels")) {
  985. for (Channel chan : channels.values()) {
  986. chan.join();
  987. }
  988. }
  989. for (String channel : autochannels) {
  990. parser.joinChannel(channel);
  991. }
  992. // Check we have mode aliases
  993. final String modes = parser.getBoolChanModes() + parser.getListChanModes()
  994. + parser.getSetOnlyChanModes() + parser.getSetUnsetChanModes();
  995. for (int i = 0; i < modes.length(); i++) {
  996. final char mode = modes.charAt(i);
  997. if (!configManager.hasOption(DOMAIN_SERVER, "mode" + mode)) {
  998. Logger.appError(ErrorLevel.LOW, "No mode alias for mode +" + mode,
  999. new Exception("No mode alias for mode +" + mode + "\n" // NOPMD
  1000. + "Network: " + parser.getNetworkName() + "\n"
  1001. + "IRCd: " + parser.getIRCD(false)
  1002. + " (" + parser.getIRCD(true) + ")\n\n"));
  1003. }
  1004. }
  1005. }
  1006. // --------------------------------------------------- INVITE HANDLING -----
  1007. /**
  1008. * Adds an invite listener to this server.
  1009. *
  1010. * @param listener The listener to be added
  1011. */
  1012. public void addInviteListener(final InviteListener listener) {
  1013. listeners.add(InviteListener.class, listener);
  1014. }
  1015. /**
  1016. * Removes an invite listener from this server.
  1017. *
  1018. * @param listener The listener to be removed
  1019. */
  1020. public void removeInviteListener(final InviteListener listener) {
  1021. listeners.remove(InviteListener.class, listener);
  1022. }
  1023. /**
  1024. * Adds an invite to this server, and fires the appropriate listeners.
  1025. *
  1026. * @param invite The invite to be added
  1027. */
  1028. public void addInvite(final Invite invite) {
  1029. invites.add(invite);
  1030. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1031. listener.inviteReceived(this, invite);
  1032. }
  1033. }
  1034. /**
  1035. * Removes an invite from this server, and fires the appropriate listeners.
  1036. *
  1037. * @param invite The invite to be removed
  1038. */
  1039. public void removeInvite(final Invite invite) {
  1040. invites.remove(invite);
  1041. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1042. listener.inviteExpired(this, invite);
  1043. }
  1044. }
  1045. // ----------------------------------------------- AWAY STATE HANDLING -----
  1046. /**
  1047. * Adds an away state lisener to this server.
  1048. *
  1049. * @param listener The listener to be added
  1050. */
  1051. public void addAwayStateListener(final AwayStateListener listener) {
  1052. listeners.add(AwayStateListener.class, listener);
  1053. }
  1054. /**
  1055. * Removes an away state lisener from this server.
  1056. *
  1057. * @param listener The listener to be removed
  1058. */
  1059. public void removeAwayStateListener(final AwayStateListener listener) {
  1060. listeners.remove(AwayStateListener.class, listener);
  1061. }
  1062. /**
  1063. * Updates our away state and fires the relevant listeners.
  1064. *
  1065. * @param message The away message to use, or an empty string if we're not
  1066. * away
  1067. */
  1068. public void updateAwayState(final String message) {
  1069. awayMessage = message;
  1070. if (message.isEmpty()) {
  1071. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1072. listener.onBack();
  1073. }
  1074. } else {
  1075. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1076. listener.onAway(message);
  1077. }
  1078. }
  1079. }
  1080. }