Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

Server.java 42KB

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