Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

Server.java 55KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607
  1. /*
  2. * Copyright (c) 2006-2014 DMDirc Developers
  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.commandparser.CommandType;
  24. import com.dmdirc.commandparser.parsers.CommandParser;
  25. import com.dmdirc.events.ChannelOpenedEvent;
  26. import com.dmdirc.events.EventUtils;
  27. import com.dmdirc.events.QueryOpenedEvent;
  28. import com.dmdirc.events.ServerConnectErrorEvent;
  29. import com.dmdirc.events.ServerConnectedEvent;
  30. import com.dmdirc.events.ServerConnectingEvent;
  31. import com.dmdirc.events.ServerDisconnectedEvent;
  32. import com.dmdirc.events.ServerNopingEvent;
  33. import com.dmdirc.events.ServerNumericEvent;
  34. import com.dmdirc.interfaces.AwayStateListener;
  35. import com.dmdirc.interfaces.Connection;
  36. import com.dmdirc.interfaces.InviteListener;
  37. import com.dmdirc.interfaces.config.ConfigChangeListener;
  38. import com.dmdirc.interfaces.config.ConfigProvider;
  39. import com.dmdirc.interfaces.config.ConfigProviderMigrator;
  40. import com.dmdirc.interfaces.config.IdentityFactory;
  41. import com.dmdirc.logger.ErrorLevel;
  42. import com.dmdirc.logger.Logger;
  43. import com.dmdirc.messages.MessageSinkManager;
  44. import com.dmdirc.parser.common.ChannelJoinRequest;
  45. import com.dmdirc.parser.common.DefaultStringConverter;
  46. import com.dmdirc.parser.common.IgnoreList;
  47. import com.dmdirc.parser.common.MyInfo;
  48. import com.dmdirc.parser.common.ParserError;
  49. import com.dmdirc.parser.common.ThreadedParser;
  50. import com.dmdirc.parser.interfaces.ChannelInfo;
  51. import com.dmdirc.parser.interfaces.ClientInfo;
  52. import com.dmdirc.parser.interfaces.EncodingParser;
  53. import com.dmdirc.parser.interfaces.Parser;
  54. import com.dmdirc.parser.interfaces.ProtocolDescription;
  55. import com.dmdirc.parser.interfaces.SecureParser;
  56. import com.dmdirc.parser.interfaces.StringConverter;
  57. import com.dmdirc.tls.CertificateManager;
  58. import com.dmdirc.tls.CertificateProblemListener;
  59. import com.dmdirc.ui.StatusMessage;
  60. import com.dmdirc.ui.WindowManager;
  61. import com.dmdirc.ui.core.components.StatusBarManager;
  62. import com.dmdirc.ui.core.components.WindowComponent;
  63. import com.dmdirc.ui.input.TabCompleterFactory;
  64. import com.dmdirc.ui.input.TabCompletionType;
  65. import com.dmdirc.ui.messages.Formatter;
  66. import com.dmdirc.util.URLBuilder;
  67. import java.net.URI;
  68. import java.net.URISyntaxException;
  69. import java.security.cert.CertificateException;
  70. import java.security.cert.X509Certificate;
  71. import java.util.ArrayList;
  72. import java.util.Arrays;
  73. import java.util.Collection;
  74. import java.util.Collections;
  75. import java.util.Date;
  76. import java.util.HashSet;
  77. import java.util.List;
  78. import java.util.Map;
  79. import java.util.Random;
  80. import java.util.Set;
  81. import java.util.concurrent.ConcurrentSkipListMap;
  82. import java.util.concurrent.ScheduledExecutorService;
  83. import java.util.concurrent.ScheduledFuture;
  84. import java.util.concurrent.TimeUnit;
  85. import java.util.concurrent.locks.ReadWriteLock;
  86. import java.util.concurrent.locks.ReentrantReadWriteLock;
  87. import javax.annotation.Nonnull;
  88. import javax.net.ssl.TrustManager;
  89. import org.slf4j.LoggerFactory;
  90. import static com.google.common.base.Preconditions.checkArgument;
  91. import static com.google.common.base.Preconditions.checkNotNull;
  92. /**
  93. * The Server class represents the client's view of a server. It maintains a list of all channels,
  94. * queries, etc, and handles parser callbacks pertaining to the server.
  95. */
  96. public class Server extends FrameContainer implements ConfigChangeListener,
  97. CertificateProblemListener, Connection {
  98. private static final org.slf4j.Logger log = LoggerFactory.getLogger(Server.class);
  99. /** The name of the general domain. */
  100. private static final String DOMAIN_GENERAL = "general";
  101. /** The name of the profile domain. */
  102. private static final String DOMAIN_PROFILE = "profile";
  103. /** The name of the server domain. */
  104. private static final String DOMAIN_SERVER = "server";
  105. /** Open channels that currently exist on the server. */
  106. private final ChannelMap channels = new ChannelMap();
  107. /** Open query windows on the server. */
  108. private final Map<String, Query> queries = new ConcurrentSkipListMap<>();
  109. /** The Parser instance handling this server. */
  110. private Parser parser;
  111. /** The Parser instance that used to be handling this server. */
  112. private Parser oldParser;
  113. /** The parser-supplied protocol description object. */
  114. private ProtocolDescription protocolDescription;
  115. /**
  116. * Object used to synchronise access to parser. This object should be locked by anything
  117. * requiring that the parser reference remains the same for a duration of time, or by anything
  118. * which is updating the parser reference.
  119. *
  120. * If used in conjunction with myStateLock, the parserLock must always be locked INSIDE the
  121. * myStateLock to prevent deadlocks.
  122. */
  123. private final ReadWriteLock parserLock = new ReentrantReadWriteLock();
  124. /** The raw frame used for this server instance. */
  125. private Raw raw;
  126. /** The address of the server we're connecting to. */
  127. private URI address;
  128. /** The profile we're using. */
  129. private ConfigProvider profile;
  130. /** Object used to synchronise access to myState. */
  131. private final Object myStateLock = new Object();
  132. /** The current state of this server. */
  133. private final ServerStatus myState = new ServerStatus(this, myStateLock);
  134. /** Our reason for being away, if any. */
  135. private String awayMessage;
  136. /** Our event handler. */
  137. private final ServerEventHandler eventHandler;
  138. /** A list of outstanding invites. */
  139. private final List<Invite> invites = new ArrayList<>();
  140. /** A set of channels we want to join without focusing. */
  141. private final Set<String> backgroundChannels = new HashSet<>();
  142. /** Our ignore list. */
  143. private final IgnoreList ignoreList = new IgnoreList();
  144. /** Our string convertor. */
  145. private StringConverter converter = new DefaultStringConverter();
  146. /** The certificate manager in use, if any. */
  147. private CertificateManager certificateManager;
  148. /** ParserFactory we use for creating parsers. */
  149. private final ParserFactory parserFactory;
  150. /** ServerManager that created us. */
  151. private final ServerManager manager;
  152. /** Factory to use to create new identities. */
  153. private final IdentityFactory identityFactory;
  154. /** Window manager to pas to children. */
  155. private final WindowManager windowManager;
  156. /** The migrator to use to change our config provider. */
  157. private final ConfigProviderMigrator configMigrator;
  158. /** Factory to use for creating channels. */
  159. private final ChannelFactory channelFactory;
  160. /** Factory to use for creating queries. */
  161. private final QueryFactory queryFactory;
  162. /** Factory to use for creating raw windows. */
  163. private final RawFactory rawFactory;
  164. /** The config provider to write user settings to. */
  165. private final ConfigProvider userSettings;
  166. /** The manager to use to add status bar messages. */
  167. private final StatusBarManager statusBarManager;
  168. /** Executor service to use to schedule repeated events. */
  169. private final ScheduledExecutorService executorService;
  170. /** The message encoder factory to create a message encoder with. */
  171. private final MessageEncoderFactory messageEncoderFactory;
  172. /** The future used when a who timer is scheduled. */
  173. private ScheduledFuture<?> whoTimerFuture;
  174. /** The future used when a reconnect timer is scheduled. */
  175. private ScheduledFuture<?> reconnectTimerFuture;
  176. /**
  177. * Creates a new server which will connect to the specified URL with the specified profile.
  178. *
  179. * @since 0.6.3
  180. * @param manager The server manager that owns this server.
  181. * @param configMigrator The migrateable configuration manager to read config settings
  182. * from.
  183. * @param commandParser The parser to use for commands in this server's window.
  184. * @param parserFactory The factory to use to generate parsers.
  185. * @param tabCompleterFactory The factory to use for tab completers.
  186. * @param identityFactory The factory to use to create identities.
  187. * @param messageSinkManager The sink manager to use to dispatch messages.
  188. * @param statusBarManager The manager to use to add status bar messages.
  189. * @param windowManager Window Manager
  190. * @param channelFactory The factory to use to create channels.
  191. * @param queryFactory The factory to use to create queries.
  192. * @param rawFactory The factory to use to create raw windows.
  193. * @param urlBuilder The URL builder to use when finding icons.
  194. * @param eventBus The event bus to dispatch events onto.
  195. * @param messageEncoderFactory The message encoder factory to create a message encoder with.
  196. * @param userSettings The config provider to write user settings to.
  197. * @param executorService The service to use to schedule events.
  198. * @param uri The address of the server to connect to
  199. * @param profile The profile to use
  200. */
  201. public Server(
  202. final ServerManager manager,
  203. final ConfigProviderMigrator configMigrator,
  204. final CommandParser commandParser,
  205. final ParserFactory parserFactory,
  206. final TabCompleterFactory tabCompleterFactory,
  207. final IdentityFactory identityFactory,
  208. final MessageSinkManager messageSinkManager,
  209. final StatusBarManager statusBarManager,
  210. final WindowManager windowManager,
  211. final ChannelFactory channelFactory,
  212. final QueryFactory queryFactory,
  213. final RawFactory rawFactory,
  214. final URLBuilder urlBuilder,
  215. final DMDircMBassador eventBus,
  216. final MessageEncoderFactory messageEncoderFactory,
  217. final ConfigProvider userSettings,
  218. final ScheduledExecutorService executorService,
  219. final URI uri,
  220. final ConfigProvider profile) {
  221. super(null, "server-disconnected",
  222. getHost(uri),
  223. getHost(uri),
  224. configMigrator.getConfigProvider(),
  225. urlBuilder,
  226. commandParser,
  227. tabCompleterFactory.getTabCompleter(configMigrator.getConfigProvider(),
  228. CommandType.TYPE_SERVER, CommandType.TYPE_GLOBAL),
  229. messageSinkManager,
  230. eventBus,
  231. Arrays.asList(
  232. WindowComponent.TEXTAREA.getIdentifier(),
  233. WindowComponent.INPUTFIELD.getIdentifier(),
  234. WindowComponent.CERTIFICATE_VIEWER.getIdentifier()));
  235. this.manager = manager;
  236. this.parserFactory = parserFactory;
  237. this.identityFactory = identityFactory;
  238. this.windowManager = windowManager;
  239. this.configMigrator = configMigrator;
  240. this.channelFactory = channelFactory;
  241. this.queryFactory = queryFactory;
  242. this.rawFactory = rawFactory;
  243. this.executorService = executorService;
  244. this.userSettings = userSettings;
  245. this.statusBarManager = statusBarManager;
  246. this.messageEncoderFactory = messageEncoderFactory;
  247. eventHandler = new ServerEventHandler(this, eventBus);
  248. setConnectionDetails(uri, profile);
  249. updateIcon();
  250. getConfigManager().addChangeListener("formatter", "serverName", this);
  251. getConfigManager().addChangeListener("formatter", "serverTitle", this);
  252. }
  253. /**
  254. * Updates the connection details for this server. If the specified URI does not define a port,
  255. * the default port from the protocol description will be used.
  256. *
  257. * @param uri The new URI that this server should connect to
  258. * @param profile The profile that this server should use
  259. */
  260. private void setConnectionDetails(final URI uri, final ConfigProvider profile) {
  261. this.address = uri;
  262. this.protocolDescription = parserFactory.getDescription(uri);
  263. this.profile = profile;
  264. if (uri.getPort() == -1 && protocolDescription != null) {
  265. try {
  266. this.address = new URI(uri.getScheme(), uri.getUserInfo(),
  267. uri.getHost(), protocolDescription.getDefaultPort(),
  268. uri.getPath(), uri.getQuery(), uri.getFragment());
  269. } catch (URISyntaxException ex) {
  270. Logger.appError(ErrorLevel.MEDIUM, "Unable to construct URI", ex);
  271. }
  272. }
  273. }
  274. @Override
  275. public void connect() {
  276. connect(address, profile);
  277. }
  278. @Override
  279. @Precondition({
  280. "The current parser is null or not connected",
  281. "The specified profile is not null"
  282. })
  283. @SuppressWarnings("fallthrough")
  284. public void connect(final URI address, final ConfigProvider profile) {
  285. assert profile != null;
  286. synchronized (myStateLock) {
  287. log.info("Connecting to {}, current state is {}", address,
  288. myState.getState());
  289. switch (myState.getState()) {
  290. case RECONNECT_WAIT:
  291. log.debug("Cancelling reconnection timer");
  292. if (reconnectTimerFuture != null) {
  293. reconnectTimerFuture.cancel(false);
  294. }
  295. break;
  296. case CLOSING:
  297. // Ignore the connection attempt
  298. return;
  299. case CONNECTED:
  300. case CONNECTING:
  301. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  302. case DISCONNECTING:
  303. while (!myState.getState().isDisconnected()) {
  304. try {
  305. myStateLock.wait();
  306. } catch (InterruptedException ex) {
  307. return;
  308. }
  309. }
  310. break;
  311. default:
  312. // Do nothing
  313. break;
  314. }
  315. final URI connectAddress;
  316. try {
  317. parserLock.writeLock().lock();
  318. if (parser != null) {
  319. throw new IllegalArgumentException("Connection attempt while parser "
  320. + "is still connected.\n\nMy state:" + getState());
  321. }
  322. configMigrator.migrate(address.getScheme(), "", "", address.getHost());
  323. setConnectionDetails(address, profile);
  324. updateTitle();
  325. updateIcon();
  326. parser = buildParser();
  327. if (parser == null) {
  328. addLine("serverUnknownProtocol", address.getScheme());
  329. return;
  330. }
  331. connectAddress = parser.getURI();
  332. } finally {
  333. parserLock.writeLock().unlock();
  334. }
  335. addLine("serverConnecting", connectAddress.getHost(), connectAddress.getPort());
  336. myState.transition(ServerState.CONNECTING);
  337. doCallbacks();
  338. updateAwayState(null);
  339. removeInvites();
  340. parser.connect();
  341. if (parser instanceof ThreadedParser) {
  342. ((ThreadedParser) parser).getControlThread().setName("Parser - " + connectAddress.
  343. getHost());
  344. }
  345. }
  346. getEventBus().publish(new ServerConnectingEvent(this));
  347. }
  348. @Override
  349. public void reconnect(final String reason) {
  350. synchronized (myStateLock) {
  351. if (myState.getState() == ServerState.CLOSING) {
  352. return;
  353. }
  354. disconnect(reason);
  355. connect(address, profile);
  356. }
  357. }
  358. @Override
  359. public void reconnect() {
  360. reconnect(getConfigManager().getOption(DOMAIN_GENERAL, "reconnectmessage"));
  361. }
  362. @Override
  363. public void disconnect() {
  364. disconnect(getConfigManager().getOption(DOMAIN_GENERAL, "quitmessage"));
  365. }
  366. @Override
  367. public void disconnect(final String reason) {
  368. synchronized (myStateLock) {
  369. log.info("Disconnecting. Current state: {}", myState.getState());
  370. switch (myState.getState()) {
  371. case CLOSING:
  372. case DISCONNECTING:
  373. case DISCONNECTED:
  374. case TRANSIENTLY_DISCONNECTED:
  375. return;
  376. case RECONNECT_WAIT:
  377. log.debug("Cancelling reconnection timer");
  378. if (reconnectTimerFuture != null) {
  379. reconnectTimerFuture.cancel(false);
  380. }
  381. break;
  382. default:
  383. break;
  384. }
  385. channels.resetAll();
  386. backgroundChannels.clear();
  387. try {
  388. parserLock.readLock().lock();
  389. if (parser == null) {
  390. myState.transition(ServerState.DISCONNECTED);
  391. } else {
  392. myState.transition(ServerState.DISCONNECTING);
  393. removeInvites();
  394. updateIcon();
  395. parser.disconnect(reason);
  396. }
  397. } finally {
  398. parserLock.readLock().unlock();
  399. }
  400. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closechannelsonquit")) {
  401. channels.closeAll();
  402. }
  403. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closequeriesonquit")) {
  404. closeQueries();
  405. }
  406. }
  407. }
  408. /**
  409. * Schedules a reconnect attempt to be performed after a user-defined delay.
  410. */
  411. @Precondition("The server state is transiently disconnected")
  412. private void doDelayedReconnect() {
  413. synchronized (myStateLock) {
  414. log.info("Performing delayed reconnect. State: {}", myState.getState());
  415. if (myState.getState() != ServerState.TRANSIENTLY_DISCONNECTED) {
  416. throw new IllegalStateException("doDelayedReconnect when not "
  417. + "transiently disconnected\n\nState: " + myState);
  418. }
  419. final int delay = Math.max(1000,
  420. getConfigManager().getOptionInt(DOMAIN_GENERAL, "reconnectdelay"));
  421. handleNotification("connectRetry", getAddress(), delay / 1000);
  422. reconnectTimerFuture = executorService.schedule(new Runnable() {
  423. @Override
  424. public void run() {
  425. synchronized (myStateLock) {
  426. log.debug("Reconnect task executing, state: {}",
  427. myState.getState());
  428. if (myState.getState() == ServerState.RECONNECT_WAIT) {
  429. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  430. reconnect();
  431. }
  432. }
  433. }
  434. }, delay, TimeUnit.MILLISECONDS);
  435. log.info("Scheduling reconnect task for delay of {}", delay);
  436. myState.transition(ServerState.RECONNECT_WAIT);
  437. updateIcon();
  438. }
  439. }
  440. @Override
  441. public boolean hasChannel(final String channel) {
  442. return channels.contains(channel);
  443. }
  444. @Override
  445. public Channel getChannel(final String channel) {
  446. return channels.get(channel).orNull();
  447. }
  448. @Override
  449. public List<String> getChannels() {
  450. return channels.getNames();
  451. }
  452. @Override
  453. public boolean hasQuery(final String host) {
  454. return queries.containsKey(converter.toLowerCase(parseHostmask(host)[0]));
  455. }
  456. @Override
  457. public Query getQuery(final String host) {
  458. return getQuery(host, false);
  459. }
  460. @Override
  461. public Query getQuery(final String host, final boolean focus) {
  462. synchronized (myStateLock) {
  463. if (myState.getState() == ServerState.CLOSING) {
  464. // Can't open queries while the server is closing
  465. return null;
  466. }
  467. }
  468. final String nick = parseHostmask(host)[0];
  469. final String lnick = converter.toLowerCase(nick);
  470. if (!queries.containsKey(lnick)) {
  471. final Query newQuery = queryFactory.getQuery(this, host);
  472. windowManager.addWindow(this, newQuery, focus);
  473. getEventBus().publish(new QueryOpenedEvent(newQuery));
  474. getTabCompleter().addEntry(TabCompletionType.QUERY_NICK, nick);
  475. queries.put(lnick, newQuery);
  476. }
  477. return queries.get(lnick);
  478. }
  479. @Override
  480. public void updateQuery(final Query query, final String oldNick, final String newNick) {
  481. getTabCompleter().removeEntry(TabCompletionType.QUERY_NICK, oldNick);
  482. getTabCompleter().addEntry(TabCompletionType.QUERY_NICK, newNick);
  483. queries.put(converter.toLowerCase(newNick), query);
  484. queries.remove(converter.toLowerCase(oldNick));
  485. }
  486. @Override
  487. public Collection<Query> getQueries() {
  488. return Collections.unmodifiableCollection(queries.values());
  489. }
  490. @Override
  491. public void delQuery(final Query query) {
  492. getTabCompleter().removeEntry(TabCompletionType.QUERY_NICK, query.getNickname());
  493. queries.remove(converter.toLowerCase(query.getNickname()));
  494. }
  495. @Override
  496. public void addRaw() {
  497. if (raw == null) {
  498. raw = rawFactory.getRaw(this);
  499. windowManager.addWindow(this, raw);
  500. try {
  501. parserLock.readLock().lock();
  502. if (parser != null) {
  503. raw.registerCallbacks();
  504. }
  505. } finally {
  506. parserLock.readLock().unlock();
  507. }
  508. }
  509. }
  510. @Override
  511. public void delRaw() {
  512. raw = null; //NOPMD
  513. }
  514. @Override
  515. public void delChannel(final String chan) {
  516. getTabCompleter().removeEntry(TabCompletionType.CHANNEL, chan);
  517. channels.remove(chan);
  518. }
  519. @Override
  520. public Channel addChannel(final ChannelInfo chan) {
  521. return addChannel(chan, !backgroundChannels.contains(chan.getName())
  522. || getConfigManager().getOptionBool(DOMAIN_GENERAL,
  523. "hidechannels"));
  524. }
  525. @Override
  526. public Channel addChannel(final ChannelInfo chan, final boolean focus) {
  527. synchronized (myStateLock) {
  528. if (myState.getState() == ServerState.CLOSING) {
  529. // Can't join channels while the server is closing
  530. return null;
  531. }
  532. }
  533. backgroundChannels.remove(chan.getName());
  534. if (hasChannel(chan.getName())) {
  535. getChannel(chan.getName()).setChannelInfo(chan);
  536. getChannel(chan.getName()).selfJoin();
  537. } else {
  538. final ConfigProviderMigrator channelConfig = identityFactory.createMigratableConfig(
  539. getProtocol(), getIrcd(), getNetwork(), getAddress(), chan.getName());
  540. final Channel newChan = channelFactory.getChannel(this, chan, channelConfig);
  541. windowManager.addWindow(this, newChan, focus);
  542. getEventBus().publish(new ChannelOpenedEvent(newChan));
  543. getTabCompleter().addEntry(TabCompletionType.CHANNEL, chan.getName());
  544. channels.add(newChan);
  545. }
  546. return getChannel(chan.getName());
  547. }
  548. /**
  549. * Closes all open query windows associated with this server.
  550. */
  551. private void closeQueries() {
  552. for (Query query : new ArrayList<>(queries.values())) {
  553. query.close();
  554. }
  555. }
  556. /**
  557. * Retrieves the host component of the specified URI, or throws a relevant exception if this is
  558. * not possible.
  559. *
  560. * @param uri The URI to be processed
  561. *
  562. * @return The URI's host component, as returned by {@link URI#getHost()}.
  563. *
  564. * @throws NullPointerException If <code>uri</code> is null
  565. * @throws IllegalArgumentException If the specified URI has no host
  566. * @since 0.6.4
  567. */
  568. private static String getHost(final URI uri) {
  569. if (uri.getHost() == null) {
  570. throw new IllegalArgumentException("URIs must have hosts");
  571. }
  572. return uri.getHost();
  573. }
  574. /**
  575. * Builds an appropriately configured {@link Parser} for this server.
  576. *
  577. * @return A configured parser.
  578. */
  579. private Parser buildParser() {
  580. final MyInfo myInfo = buildMyInfo();
  581. final Parser myParser = parserFactory.getParser(myInfo, address);
  582. if (myParser instanceof SecureParser) {
  583. certificateManager = new CertificateManager(
  584. address.getHost(), getConfigManager(), userSettings);
  585. final SecureParser secureParser = (SecureParser) myParser;
  586. secureParser.setTrustManagers(new TrustManager[]{certificateManager});
  587. secureParser.setKeyManagers(certificateManager.getKeyManager());
  588. certificateManager.addCertificateProblemListener(this);
  589. }
  590. if (myParser instanceof EncodingParser) {
  591. final EncodingParser encodingParser = (EncodingParser) myParser;
  592. encodingParser.setEncoder(messageEncoderFactory.getMessageEncoder(this, myParser));
  593. }
  594. if (myParser != null) {
  595. myParser.setIgnoreList(ignoreList);
  596. myParser.setPingTimerInterval(getConfigManager().getOptionInt(DOMAIN_SERVER,
  597. "pingtimer"));
  598. myParser.setPingTimerFraction((int) (getConfigManager().getOptionInt(DOMAIN_SERVER,
  599. "pingfrequency") / myParser.getPingTimerInterval()));
  600. if (getConfigManager().hasOptionString(DOMAIN_GENERAL, "bindip")) {
  601. myParser.setBindIP(getConfigManager().getOption(DOMAIN_GENERAL, "bindip"));
  602. }
  603. myParser.setProxy(buildProxyURI());
  604. }
  605. return myParser;
  606. }
  607. /**
  608. * Constructs a URI for the configured proxy for this server, if any.
  609. *
  610. * @return An appropriate URI or null if no proxy is configured
  611. */
  612. private URI buildProxyURI() {
  613. if (getConfigManager().hasOptionString(DOMAIN_SERVER, "proxy.address")) {
  614. final String type;
  615. if (getConfigManager().hasOptionString(DOMAIN_SERVER, "proxy.type")) {
  616. type = getConfigManager().getOption(DOMAIN_SERVER, "proxy.type");
  617. } else {
  618. type = "socks";
  619. }
  620. final int port;
  621. if (getConfigManager().hasOptionInt(DOMAIN_SERVER, "proxy.port")) {
  622. port = getConfigManager().getOptionInt(DOMAIN_SERVER, "proxy.port");
  623. } else {
  624. port = 8080;
  625. }
  626. final String host = getConfigManager().getOptionString(DOMAIN_SERVER, "proxy.address");
  627. final String userInfo;
  628. if (getConfigManager().hasOptionString(DOMAIN_SERVER, "proxy.username")
  629. && getConfigManager().hasOptionString(DOMAIN_SERVER, "proxy.password")) {
  630. userInfo = getConfigManager().getOption(DOMAIN_SERVER, "proxy.username")
  631. + getConfigManager().getOption(DOMAIN_SERVER, "proxy.password");
  632. } else {
  633. userInfo = "";
  634. }
  635. try {
  636. return new URI(type, userInfo, host, port, "", "", "");
  637. } catch (URISyntaxException ex) {
  638. Logger.appError(ErrorLevel.MEDIUM, "Unable to create proxy URI", ex);
  639. }
  640. }
  641. return null;
  642. }
  643. @Override
  644. public boolean compareURI(final URI uri) {
  645. if (parser != null) {
  646. return parser.compareURI(uri);
  647. }
  648. return oldParser != null && oldParser.compareURI(uri);
  649. }
  650. @Override
  651. public String[] parseHostmask(final String hostmask) {
  652. return protocolDescription.parseHostmask(hostmask);
  653. }
  654. /**
  655. * Retrieves the MyInfo object used for the Parser.
  656. *
  657. * @return The MyInfo object for our profile
  658. */
  659. @Precondition({
  660. "The current profile is not null",
  661. "The current profile specifies at least one nickname"
  662. })
  663. private MyInfo buildMyInfo() {
  664. checkNotNull(profile);
  665. checkArgument(!profile.getOptionList(DOMAIN_PROFILE, "nicknames").isEmpty());
  666. final MyInfo myInfo = new MyInfo();
  667. myInfo.setNickname(profile.getOptionList(DOMAIN_PROFILE, "nicknames").get(0));
  668. myInfo.setRealname(profile.getOption(DOMAIN_PROFILE, "realname"));
  669. if (profile.hasOptionString(DOMAIN_PROFILE, "ident")) {
  670. myInfo.setUsername(profile.getOption(DOMAIN_PROFILE, "ident"));
  671. }
  672. return myInfo;
  673. }
  674. /**
  675. * Updates this server's icon.
  676. */
  677. private void updateIcon() {
  678. final String icon = myState.getState() == ServerState.CONNECTED
  679. ? protocolDescription.isSecure(address)
  680. ? "secure-server" : "server" : "server-disconnected";
  681. setIcon(icon);
  682. }
  683. /**
  684. * Registers callbacks.
  685. */
  686. private void doCallbacks() {
  687. if (raw != null) {
  688. raw.registerCallbacks();
  689. }
  690. eventHandler.registerCallbacks();
  691. for (Query query : queries.values()) {
  692. query.reregister();
  693. }
  694. }
  695. @Override
  696. public void join(final ChannelJoinRequest... requests) {
  697. join(true, requests);
  698. }
  699. @Override
  700. public void join(final boolean focus, final ChannelJoinRequest... requests) {
  701. synchronized (myStateLock) {
  702. if (myState.getState() == ServerState.CONNECTED) {
  703. final List<ChannelJoinRequest> pending = new ArrayList<>();
  704. for (ChannelJoinRequest request : requests) {
  705. removeInvites(request.getName());
  706. final String name;
  707. if (parser.isValidChannelName(request.getName())) {
  708. name = request.getName();
  709. } else {
  710. name = parser.getChannelPrefixes().substring(0, 1)
  711. + request.getName();
  712. }
  713. if (!hasChannel(name) || !getChannel(name).isOnChannel()) {
  714. if (!focus) {
  715. backgroundChannels.add(name);
  716. }
  717. pending.add(request);
  718. }
  719. }
  720. parser.joinChannels(pending.toArray(new ChannelJoinRequest[pending.size()]));
  721. }
  722. // TODO: otherwise: address.getChannels().add(channel);
  723. }
  724. }
  725. @Override
  726. public void sendLine(final String line) {
  727. synchronized (myStateLock) {
  728. try {
  729. parserLock.readLock().lock();
  730. if (parser != null && !line.isEmpty()
  731. && myState.getState() == ServerState.CONNECTED) {
  732. parser.sendRawMessage(line);
  733. }
  734. } finally {
  735. parserLock.readLock().unlock();
  736. }
  737. }
  738. }
  739. @Override
  740. public int getMaxLineLength() {
  741. try {
  742. parserLock.readLock().lock();
  743. return parser == null ? -1 : parser.getMaxLength();
  744. } finally {
  745. parserLock.readLock().unlock();
  746. }
  747. }
  748. @Override
  749. public Parser getParser() {
  750. return parser;
  751. }
  752. @Override
  753. public ConfigProvider getProfile() {
  754. return profile;
  755. }
  756. @Override
  757. public String getChannelPrefixes() {
  758. try {
  759. parserLock.readLock().lock();
  760. return parser == null ? "#&" : parser.getChannelPrefixes();
  761. } finally {
  762. parserLock.readLock().unlock();
  763. }
  764. }
  765. @Override
  766. public String getAddress() {
  767. try {
  768. parserLock.readLock().lock();
  769. return parser == null ? address.getHost() : parser.getServerName();
  770. } finally {
  771. parserLock.readLock().unlock();
  772. }
  773. }
  774. @Override
  775. public String getNetwork() {
  776. try {
  777. parserLock.readLock().lock();
  778. if (parser == null) {
  779. throw new IllegalStateException("getNetwork called when "
  780. + "parser is null (state: " + getState() + ")");
  781. } else if (parser.getNetworkName().isEmpty()) {
  782. return getNetworkFromServerName(parser.getServerName());
  783. } else {
  784. return parser.getNetworkName();
  785. }
  786. } finally {
  787. parserLock.readLock().unlock();
  788. }
  789. }
  790. @Override
  791. public boolean isNetwork(final String target) {
  792. synchronized (myStateLock) {
  793. try {
  794. parserLock.readLock().lock();
  795. return parser != null && getNetwork().equalsIgnoreCase(target);
  796. } finally {
  797. parserLock.readLock().unlock();
  798. }
  799. }
  800. }
  801. /**
  802. * Calculates a network name from the specified server name. This method implements parts 2-4 of
  803. * the procedure documented at getNetwork().
  804. *
  805. * @param serverName The server name to parse
  806. *
  807. * @return A network name for the specified server
  808. */
  809. protected static String getNetworkFromServerName(final String serverName) {
  810. final String[] parts = serverName.split("\\.");
  811. final String[] tlds = {"biz", "com", "info", "net", "org"};
  812. boolean isTLD = false;
  813. for (String tld : tlds) {
  814. if (serverName.endsWith("." + tld)) {
  815. isTLD = true;
  816. break;
  817. }
  818. }
  819. if (isTLD && parts.length > 2) {
  820. return parts[parts.length - 2] + "." + parts[parts.length - 1];
  821. } else if (parts.length > 2) {
  822. final StringBuilder network = new StringBuilder();
  823. for (int i = 1; i < parts.length; i++) {
  824. if (network.length() > 0) {
  825. network.append('.');
  826. }
  827. network.append(parts[i]);
  828. }
  829. return network.toString();
  830. } else {
  831. return serverName;
  832. }
  833. }
  834. @Override
  835. public String getIrcd() {
  836. return parser.getServerSoftwareType();
  837. }
  838. @Override
  839. public String getProtocol() {
  840. return address.getScheme();
  841. }
  842. @Override
  843. public boolean isAway() {
  844. return awayMessage != null;
  845. }
  846. @Override
  847. public String getAwayMessage() {
  848. return awayMessage;
  849. }
  850. @Override
  851. public ServerState getState() {
  852. return myState.getState();
  853. }
  854. @Override
  855. public ServerStatus getStatus() {
  856. return myState;
  857. }
  858. @Override
  859. public void close() {
  860. super.close();
  861. synchronized (myStateLock) {
  862. // Remove any callbacks or listeners
  863. eventHandler.unregisterCallbacks();
  864. getConfigManager().removeListener(this);
  865. executorService.shutdown();
  866. // Trigger any actions necessary
  867. disconnect();
  868. myState.transition(ServerState.CLOSING);
  869. }
  870. channels.closeAll();
  871. closeQueries();
  872. removeInvites();
  873. if (raw != null) {
  874. raw.close();
  875. }
  876. // Inform any parents that the window is closing
  877. manager.unregisterServer(this);
  878. }
  879. @Override
  880. public FrameContainer getWindowModel() {
  881. return this;
  882. }
  883. @Override
  884. public void addLineToAll(final String messageType, final Date date,
  885. final Object... args) {
  886. channels.addLineToAll(messageType, date, args);
  887. for (Query query : queries.values()) {
  888. query.addLine(messageType, date, args);
  889. }
  890. addLine(messageType, date, args);
  891. }
  892. @Override
  893. public void sendCTCPReply(final String source, final String type, final String args) {
  894. if (type.equalsIgnoreCase("VERSION")) {
  895. parser.sendCTCPReply(source, "VERSION", "DMDirc "
  896. + getConfigManager().getOption("version", "version")
  897. + " - http://www.dmdirc.com/");
  898. } else if (type.equalsIgnoreCase("PING")) {
  899. parser.sendCTCPReply(source, "PING", args);
  900. } else if (type.equalsIgnoreCase("CLIENTINFO")) {
  901. parser.sendCTCPReply(source, "CLIENTINFO", "VERSION PING CLIENTINFO");
  902. }
  903. }
  904. @Override
  905. public boolean isValidChannelName(final String channelName) {
  906. try {
  907. parserLock.readLock().lock();
  908. return hasChannel(channelName)
  909. || (parser != null && parser.isValidChannelName(channelName));
  910. } finally {
  911. parserLock.readLock().unlock();
  912. }
  913. }
  914. @Nonnull
  915. @Override
  916. public Connection getConnection() {
  917. return this;
  918. }
  919. @Override
  920. protected boolean processNotificationArg(final Object arg, final List<Object> args) {
  921. if (arg instanceof ClientInfo) {
  922. final ClientInfo clientInfo = (ClientInfo) arg;
  923. args.add(clientInfo.getNickname());
  924. args.add(clientInfo.getUsername());
  925. args.add(clientInfo.getHostname());
  926. return true;
  927. } else {
  928. return super.processNotificationArg(arg, args);
  929. }
  930. }
  931. @Override
  932. public void updateTitle() {
  933. synchronized (myStateLock) {
  934. if (myState.getState() == ServerState.CLOSING) {
  935. return;
  936. }
  937. try {
  938. parserLock.readLock().lock();
  939. final Object[] arguments = new Object[]{
  940. address.getHost(), parser == null ? "Unknown" : parser.getServerName(),
  941. address.getPort(), parser == null ? "Unknown" : getNetwork(),
  942. parser == null ? "Unknown" : parser.getLocalClient().getNickname(),};
  943. setName(Formatter.formatMessage(getConfigManager(),
  944. "serverName", arguments));
  945. setTitle(Formatter.formatMessage(getConfigManager(),
  946. "serverTitle", arguments));
  947. } finally {
  948. parserLock.readLock().unlock();
  949. }
  950. }
  951. }
  952. @Override
  953. public void configChanged(final String domain, final String key) {
  954. if ("formatter".equals(domain)) {
  955. updateTitle();
  956. }
  957. }
  958. /**
  959. * Called when the server says that the nickname we're trying to use is already in use.
  960. *
  961. * @param nickname The nickname that we were trying to use
  962. */
  963. public void onNickInUse(final String nickname) {
  964. final String lastNick = parser.getLocalClient().getNickname();
  965. // If our last nick is still valid, ignore the in use message
  966. if (!converter.equalsIgnoreCase(lastNick, nickname)) {
  967. return;
  968. }
  969. String newNick = lastNick + new Random().nextInt(10);
  970. final List<String> alts = profile.getOptionList(DOMAIN_PROFILE, "nicknames");
  971. int offset = 0;
  972. // Loop so we can check case sensitivity
  973. for (String alt : alts) {
  974. offset++;
  975. if (converter.equalsIgnoreCase(alt, lastNick)) {
  976. break;
  977. }
  978. }
  979. if (offset < alts.size() && !alts.get(offset).isEmpty()) {
  980. newNick = alts.get(offset);
  981. }
  982. parser.getLocalClient().setNickname(newNick);
  983. }
  984. /**
  985. * Called when the server sends a numeric event.
  986. *
  987. * @param numeric The numeric code for the event
  988. * @param tokens The (tokenised) arguments of the event
  989. */
  990. public void onNumeric(final int numeric, final String[] tokens) {
  991. String snumeric = String.valueOf(numeric);
  992. if (numeric < 10) {
  993. snumeric = "00" + snumeric;
  994. } else if (numeric < 100) {
  995. snumeric = "0" + snumeric;
  996. }
  997. final String sansIrcd = "numeric_" + snumeric;
  998. String target = "";
  999. if (getConfigManager().hasOptionString("formatter", sansIrcd)) {
  1000. target = sansIrcd;
  1001. } else if (getConfigManager().hasOptionString("formatter", "numeric_unknown")) {
  1002. target = "numeric_unknown";
  1003. }
  1004. final ServerNumericEvent event = new ServerNumericEvent(this, numeric, tokens);
  1005. final String format = EventUtils.postDisplayable(getEventBus(), event, target);
  1006. handleNotification(format, (Object[]) tokens);
  1007. }
  1008. /**
  1009. * Called when the socket has been closed.
  1010. */
  1011. public void onSocketClosed() {
  1012. log.info("Received socket closed event, state: {}", myState.getState());
  1013. if (whoTimerFuture != null) {
  1014. whoTimerFuture.cancel(false);
  1015. }
  1016. if (Thread.holdsLock(myStateLock)) {
  1017. log.info("State lock contended: rerunning on a new thread");
  1018. executorService.schedule(new Runnable() {
  1019. @Override
  1020. public void run() {
  1021. onSocketClosed();
  1022. }
  1023. }, 0, TimeUnit.SECONDS);
  1024. return;
  1025. }
  1026. handleNotification("socketClosed", getAddress());
  1027. getEventBus().publish(new ServerDisconnectedEvent(this));
  1028. eventHandler.unregisterCallbacks();
  1029. synchronized (myStateLock) {
  1030. if (myState.getState() == ServerState.CLOSING
  1031. || myState.getState() == ServerState.DISCONNECTED) {
  1032. // This has been triggered via .disconnect()
  1033. return;
  1034. }
  1035. if (myState.getState() == ServerState.DISCONNECTING) {
  1036. myState.transition(ServerState.DISCONNECTED);
  1037. } else {
  1038. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  1039. }
  1040. channels.resetAll();
  1041. try {
  1042. parserLock.writeLock().lock();
  1043. oldParser = parser;
  1044. parser = null;
  1045. } finally {
  1046. parserLock.writeLock().unlock();
  1047. }
  1048. updateIcon();
  1049. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closechannelsondisconnect")) {
  1050. channels.closeAll();
  1051. }
  1052. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "closequeriesondisconnect")) {
  1053. closeQueries();
  1054. }
  1055. removeInvites();
  1056. updateAwayState(null);
  1057. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  1058. "reconnectondisconnect")
  1059. && myState.getState() == ServerState.TRANSIENTLY_DISCONNECTED) {
  1060. doDelayedReconnect();
  1061. }
  1062. }
  1063. }
  1064. /**
  1065. * Called when an error was encountered while connecting.
  1066. *
  1067. * @param errorInfo The parser's error information
  1068. */
  1069. @Precondition("The current server state is CONNECTING")
  1070. public void onConnectError(final ParserError errorInfo) {
  1071. synchronized (myStateLock) {
  1072. log.info("Received connect error event, state: {}; error: {}",
  1073. myState.getState(), errorInfo);
  1074. if (myState.getState() == ServerState.CLOSING
  1075. || myState.getState() == ServerState.DISCONNECTING) {
  1076. // Do nothing
  1077. return;
  1078. } else if (myState.getState() != ServerState.CONNECTING) {
  1079. // Shouldn't happen
  1080. throw new IllegalStateException("Connect error when not "
  1081. + "connecting\n\n" + getStatus().getTransitionHistory());
  1082. }
  1083. myState.transition(ServerState.TRANSIENTLY_DISCONNECTED);
  1084. try {
  1085. parserLock.writeLock().lock();
  1086. oldParser = parser;
  1087. parser = null;
  1088. } finally {
  1089. parserLock.writeLock().unlock();
  1090. }
  1091. updateIcon();
  1092. String description;
  1093. if (errorInfo.getException() == null) {
  1094. description = errorInfo.getData();
  1095. } else {
  1096. final Exception exception = errorInfo.getException();
  1097. if (exception instanceof java.net.UnknownHostException) {
  1098. description = "Unknown host (unable to resolve)";
  1099. } else if (exception instanceof java.net.NoRouteToHostException) {
  1100. description = "No route to host";
  1101. } else if (exception instanceof java.net.SocketTimeoutException) {
  1102. description = "Connection attempt timed out";
  1103. } else if (exception instanceof java.net.SocketException
  1104. || exception instanceof javax.net.ssl.SSLException) {
  1105. description = exception.getMessage();
  1106. } else {
  1107. Logger.appError(ErrorLevel.LOW, "Unknown socket error: "
  1108. + exception.getClass().getCanonicalName(),
  1109. new IllegalArgumentException(exception));
  1110. description = "Unknown error: " + exception.getMessage();
  1111. }
  1112. }
  1113. getEventBus().publish(new ServerConnectErrorEvent(this, description));
  1114. handleNotification("connectError", getAddress(), description);
  1115. if (getConfigManager().getOptionBool(DOMAIN_GENERAL,
  1116. "reconnectonconnectfailure")) {
  1117. doDelayedReconnect();
  1118. }
  1119. }
  1120. }
  1121. /**
  1122. * Called when we fail to receive a ping reply within a set period of time.
  1123. */
  1124. public void onPingFailed() {
  1125. statusBarManager.setMessage(new StatusMessage(
  1126. "No ping reply from " + getName() + " for over "
  1127. + ((int) (Math.floor(parser.getPingTime() / 1000.0)))
  1128. + " seconds.", getConfigManager()));
  1129. getEventBus().publishAsync(new ServerNopingEvent(this, parser.getPingTime()));
  1130. if (parser.getPingTime()
  1131. >= getConfigManager().getOptionInt(DOMAIN_SERVER, "pingtimeout")) {
  1132. log.warn("Server appears to be stoned, reconnecting");
  1133. handleNotification("stonedServer", getAddress());
  1134. reconnect();
  1135. }
  1136. }
  1137. /**
  1138. * Called after the parser receives the 005 headers from the server.
  1139. */
  1140. @Precondition("State is CONNECTING")
  1141. public void onPost005() {
  1142. synchronized (myStateLock) {
  1143. if (myState.getState() != ServerState.CONNECTING) {
  1144. // Shouldn't happen
  1145. throw new IllegalStateException("Received onPost005 while not "
  1146. + "connecting\n\n" + myState.getTransitionHistory());
  1147. }
  1148. myState.transition(ServerState.CONNECTED);
  1149. configMigrator.migrate(address.getScheme(),
  1150. parser.getServerSoftwareType(), getNetwork(), parser.getServerName());
  1151. updateIcon();
  1152. updateTitle();
  1153. updateIgnoreList();
  1154. converter = parser.getStringConverter();
  1155. channels.setStringConverter(converter);
  1156. final List<ChannelJoinRequest> requests = new ArrayList<>();
  1157. if (getConfigManager().getOptionBool(DOMAIN_GENERAL, "rejoinchannels")) {
  1158. requests.addAll(channels.asJoinRequests());
  1159. }
  1160. join(requests.toArray(new ChannelJoinRequest[requests.size()]));
  1161. checkModeAliases();
  1162. final int whoTime = getConfigManager().getOptionInt(DOMAIN_GENERAL, "whotime");
  1163. whoTimerFuture = executorService.scheduleAtFixedRate(
  1164. channels.getWhoRunnable(), whoTime, whoTime, TimeUnit.MILLISECONDS);
  1165. }
  1166. getEventBus().publish(new ServerConnectedEvent(this));
  1167. }
  1168. /**
  1169. * Checks that we have the necessary mode aliases for this server.
  1170. */
  1171. private void checkModeAliases() {
  1172. // Check we have mode aliases
  1173. final String modes = parser.getBooleanChannelModes() + parser.getListChannelModes()
  1174. + parser.getParameterChannelModes() + parser.getDoubleParameterChannelModes();
  1175. final String umodes = parser.getUserModes();
  1176. final StringBuilder missingModes = new StringBuilder();
  1177. final StringBuilder missingUmodes = new StringBuilder();
  1178. for (char mode : modes.toCharArray()) {
  1179. if (!getConfigManager().hasOptionString(DOMAIN_SERVER, "mode" + mode)) {
  1180. missingModes.append(mode);
  1181. }
  1182. }
  1183. for (char mode : umodes.toCharArray()) {
  1184. if (!getConfigManager().hasOptionString(DOMAIN_SERVER, "umode" + mode)) {
  1185. missingUmodes.append(mode);
  1186. }
  1187. }
  1188. if (missingModes.length() + missingUmodes.length() > 0) {
  1189. final StringBuilder missing = new StringBuilder("Missing mode aliases: ");
  1190. if (missingModes.length() > 0) {
  1191. missing.append("channel: +");
  1192. missing.append(missingModes);
  1193. }
  1194. if (missingUmodes.length() > 0) {
  1195. if (missingModes.length() > 0) {
  1196. missing.append(' ');
  1197. }
  1198. missing.append("user: +");
  1199. missing.append(missingUmodes);
  1200. }
  1201. Logger.appError(ErrorLevel.LOW, missing.toString() + " ["
  1202. + parser.getServerSoftwareType() + "]",
  1203. new MissingModeAliasException(getNetwork(), parser,
  1204. getConfigManager().getOption("identity",
  1205. "modealiasversion"), missing.toString()));
  1206. }
  1207. }
  1208. @Override
  1209. public IgnoreList getIgnoreList() {
  1210. return ignoreList;
  1211. }
  1212. @Override
  1213. public void updateIgnoreList() {
  1214. ignoreList.clear();
  1215. ignoreList.addAll(getConfigManager().getOptionList("network", "ignorelist"));
  1216. }
  1217. @Override
  1218. public void saveIgnoreList() {
  1219. getNetworkIdentity().setOption("network", "ignorelist", ignoreList.getRegexList());
  1220. }
  1221. @Override
  1222. public ConfigProvider getServerIdentity() {
  1223. return identityFactory.createServerConfig(parser.getServerName());
  1224. }
  1225. @Override
  1226. public ConfigProvider getNetworkIdentity() {
  1227. return identityFactory.createNetworkConfig(getNetwork());
  1228. }
  1229. @Override
  1230. public void addInviteListener(final InviteListener listener) {
  1231. synchronized (listeners) {
  1232. listeners.add(InviteListener.class, listener);
  1233. }
  1234. }
  1235. @Override
  1236. public void removeInviteListener(final InviteListener listener) {
  1237. synchronized (listeners) {
  1238. listeners.remove(InviteListener.class, listener);
  1239. }
  1240. }
  1241. @Override
  1242. public void addInvite(final Invite invite) {
  1243. synchronized (invites) {
  1244. for (Invite oldInvite : new ArrayList<>(invites)) {
  1245. if (oldInvite.getChannel().equals(invite.getChannel())) {
  1246. removeInvite(oldInvite);
  1247. }
  1248. }
  1249. invites.add(invite);
  1250. synchronized (listeners) {
  1251. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1252. listener.inviteReceived(this, invite);
  1253. }
  1254. }
  1255. }
  1256. }
  1257. @Override
  1258. public void acceptInvites(final Invite... invites) {
  1259. final ChannelJoinRequest[] requests = new ChannelJoinRequest[invites.length];
  1260. for (int i = 0; i < invites.length; i++) {
  1261. requests[i] = new ChannelJoinRequest(invites[i].getChannel());
  1262. }
  1263. join(requests);
  1264. }
  1265. @Override
  1266. public void acceptInvites() {
  1267. synchronized (invites) {
  1268. acceptInvites(invites.toArray(new Invite[invites.size()]));
  1269. }
  1270. }
  1271. @Override
  1272. public void removeInvites(final String channel) {
  1273. for (Invite invite : new ArrayList<>(invites)) {
  1274. if (invite.getChannel().equals(channel)) {
  1275. removeInvite(invite);
  1276. }
  1277. }
  1278. }
  1279. @Override
  1280. public void removeInvites() {
  1281. for (Invite invite : new ArrayList<>(invites)) {
  1282. removeInvite(invite);
  1283. }
  1284. }
  1285. @Override
  1286. public void removeInvite(final Invite invite) {
  1287. synchronized (invites) {
  1288. invites.remove(invite);
  1289. synchronized (listeners) {
  1290. for (InviteListener listener : listeners.get(InviteListener.class)) {
  1291. listener.inviteExpired(this, invite);
  1292. }
  1293. }
  1294. }
  1295. }
  1296. @Override
  1297. public List<Invite> getInvites() {
  1298. return invites;
  1299. }
  1300. @Override
  1301. public void addAwayStateListener(final AwayStateListener listener) {
  1302. synchronized (listeners) {
  1303. listeners.add(AwayStateListener.class, listener);
  1304. }
  1305. }
  1306. @Override
  1307. public void removeAwayStateListener(final AwayStateListener listener) {
  1308. synchronized (listeners) {
  1309. listeners.remove(AwayStateListener.class, listener);
  1310. }
  1311. }
  1312. @Override
  1313. public void updateAwayState(final String message) {
  1314. if ((awayMessage != null && awayMessage.equals(message))
  1315. || (awayMessage == null && message == null)) {
  1316. return;
  1317. }
  1318. awayMessage = message;
  1319. new Thread(new Runnable() {
  1320. @Override
  1321. public void run() {
  1322. synchronized (listeners) {
  1323. if (message == null) {
  1324. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1325. listener.onBack();
  1326. }
  1327. } else {
  1328. for (AwayStateListener listener : listeners.get(AwayStateListener.class)) {
  1329. listener.onAway(message);
  1330. }
  1331. }
  1332. }
  1333. }
  1334. }, "Away state listener runner").start();
  1335. }
  1336. @Override
  1337. public void addCertificateProblemListener(final CertificateProblemListener listener) {
  1338. listeners.add(CertificateProblemListener.class, listener);
  1339. if (certificateManager != null && !certificateManager.getProblems().isEmpty()) {
  1340. listener.certificateProblemEncountered(certificateManager.getChain(),
  1341. certificateManager.getProblems(), certificateManager);
  1342. }
  1343. }
  1344. @Override
  1345. public void removeCertificateProblemListener(final CertificateProblemListener listener) {
  1346. listeners.remove(CertificateProblemListener.class, listener);
  1347. }
  1348. @Override
  1349. public void certificateProblemEncountered(final X509Certificate[] chain,
  1350. final Collection<CertificateException> problems,
  1351. final CertificateManager certificateManager) {
  1352. for (CertificateProblemListener listener : listeners.get(CertificateProblemListener.class)) {
  1353. listener.certificateProblemEncountered(chain, problems, certificateManager);
  1354. }
  1355. }
  1356. @Override
  1357. public void certificateProblemResolved(final CertificateManager manager) {
  1358. for (CertificateProblemListener listener : listeners.get(CertificateProblemListener.class)) {
  1359. listener.certificateProblemResolved(manager);
  1360. }
  1361. }
  1362. }