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

Channel.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc;
  18. import com.dmdirc.events.ChannelClosedEvent;
  19. import com.dmdirc.events.ChannelSelfActionEvent;
  20. import com.dmdirc.events.ChannelSelfJoinEvent;
  21. import com.dmdirc.events.ChannelSelfMessageEvent;
  22. import com.dmdirc.events.CommandErrorEvent;
  23. import com.dmdirc.events.NickListClientAddedEvent;
  24. import com.dmdirc.events.NickListClientRemovedEvent;
  25. import com.dmdirc.events.NickListClientsChangedEvent;
  26. import com.dmdirc.events.NickListUpdatedEvent;
  27. import com.dmdirc.interfaces.Connection;
  28. import com.dmdirc.interfaces.GroupChat;
  29. import com.dmdirc.interfaces.GroupChatUser;
  30. import com.dmdirc.interfaces.User;
  31. import com.dmdirc.interfaces.WindowModel;
  32. import com.dmdirc.config.provider.ConfigProviderMigrator;
  33. import com.dmdirc.parser.common.ChannelListModeItem;
  34. import com.dmdirc.parser.interfaces.ChannelClientInfo;
  35. import com.dmdirc.parser.interfaces.ChannelInfo;
  36. import com.dmdirc.parser.interfaces.Parser;
  37. import com.dmdirc.ui.core.components.WindowComponent;
  38. import com.dmdirc.ui.input.TabCompletionType;
  39. import com.dmdirc.ui.messages.BackBufferFactory;
  40. import com.dmdirc.ui.messages.StyledMessageUtils;
  41. import com.google.common.collect.EvictingQueue;
  42. import java.util.ArrayList;
  43. import java.util.Arrays;
  44. import java.util.Collection;
  45. import java.util.Collections;
  46. import java.util.List;
  47. import java.util.Optional;
  48. import java.util.Queue;
  49. import java.util.stream.Collectors;
  50. import javax.annotation.Nullable;
  51. /**
  52. * The Channel class represents the client's view of the channel. It handles callbacks for channel
  53. * events from the parser, maintains the corresponding ChannelWindow, and handles user input for the
  54. * channel.
  55. */
  56. public class Channel extends FrameContainer implements GroupChat {
  57. /** The parser's pChannel class. */
  58. private ChannelInfo channelInfo;
  59. /** The connection this channel is on. */
  60. private final Connection connection;
  61. /** A list of previous topics we've seen. */
  62. private final Queue<Topic> topics;
  63. /** Our event handler. */
  64. private final ChannelEventHandler eventHandler;
  65. /** The migrator to use to migrate our config provider. */
  66. private final ConfigProviderMigrator configMigrator;
  67. /** Manager used to retrieve {@link GroupChatUser}s */
  68. private final GroupChatUserManager groupChatUserManager;
  69. /** Whether we're currently in this channel or not. */
  70. private boolean isOnChannel;
  71. /** Reason for leaving the channel. */
  72. private PartReason partReason;
  73. /**
  74. * Creates a new instance of Channel.
  75. *
  76. * @param connection The connection object that this channel belongs to
  77. * @param newChannelInfo The parser's channel object that corresponds to this channel
  78. * @param configMigrator The config migrator which provides the config for this channel.
  79. */
  80. public Channel(
  81. final Connection connection,
  82. final ChannelInfo newChannelInfo,
  83. final ConfigProviderMigrator configMigrator,
  84. final BackBufferFactory backBufferFactory,
  85. final GroupChatUserManager groupChatUserManager) {
  86. super("channel-inactive",
  87. newChannelInfo.getName(),
  88. new StyledMessageUtils().stripControlCodes(newChannelInfo.getName()), // TODO: Inject this
  89. configMigrator.getConfigProvider(),
  90. backBufferFactory,
  91. connection.getWindowModel().getEventBus(),
  92. Arrays.asList(WindowComponent.TEXTAREA.getIdentifier(),
  93. WindowComponent.INPUTFIELD.getIdentifier(),
  94. WindowComponent.TOPICBAR.getIdentifier(),
  95. WindowComponent.USERLIST.getIdentifier()));
  96. this.configMigrator = configMigrator;
  97. this.channelInfo = newChannelInfo;
  98. this.connection = connection;
  99. this.groupChatUserManager = groupChatUserManager;
  100. topics = EvictingQueue.create(
  101. getConfigManager().getOptionInt("channel", "topichistorysize"));
  102. eventHandler = new ChannelEventHandler(this, getEventBus(), groupChatUserManager);
  103. initBackBuffer();
  104. registerCallbacks();
  105. updateTitle();
  106. }
  107. public ChannelInfo getChannelInfo() {
  108. return channelInfo;
  109. }
  110. @Override
  111. public boolean isOnChannel() {
  112. return isOnChannel;
  113. }
  114. @Override
  115. public PartReason getPartReason() {
  116. return partReason;
  117. }
  118. /**
  119. * Registers callbacks with the parser for this channel.
  120. */
  121. private void registerCallbacks() {
  122. eventHandler.registerCallbacks();
  123. configMigrator.migrate(connection.getProtocol(), connection.getIrcd(), connection.getNetwork(),
  124. connection.getAddress(), channelInfo.getName());
  125. }
  126. @Override
  127. public void sendLine(final String line) {
  128. if (connection.getState() != ServerState.CONNECTED
  129. || connection.getParser().get().getChannel(channelInfo.getName()) == null) {
  130. // We're not in the channel/connected to the server
  131. return;
  132. }
  133. final GroupChatUser me = getUser(connection.getLocalUser().get()).get();
  134. getInputModel().get()
  135. .splitLine(line)
  136. .stream()
  137. .filter(part -> !part.isEmpty())
  138. .forEach(part -> {
  139. getEventBus().publishAsync(new ChannelSelfMessageEvent(this, me, part));
  140. channelInfo.sendMessage(part);
  141. });
  142. }
  143. @Override
  144. public int getMaxLineLength() {
  145. return connection.getState() == ServerState.CONNECTED
  146. ? connection.getParser().get().getMaxLength("PRIVMSG", getChannelInfo().getName())
  147. : -1;
  148. }
  149. @Override
  150. public void sendAction(final String action) {
  151. if (connection.getState() != ServerState.CONNECTED
  152. || connection.getParser().get().getChannel(channelInfo.getName()) == null) {
  153. // We're not on the server/channel
  154. return;
  155. }
  156. if (connection.getParser().get().getMaxLength("PRIVMSG", getChannelInfo().getName())
  157. <= action.length()) {
  158. getEventBus().publishAsync(new CommandErrorEvent(this,
  159. "Warning: action too long to be sent"));
  160. } else {
  161. final GroupChatUser me = getUser(connection.getLocalUser().get()).get();
  162. getEventBus().publishAsync(new ChannelSelfActionEvent(this, me, action));
  163. channelInfo.sendAction(action);
  164. }
  165. }
  166. /**
  167. * Sets this object's ChannelInfo reference to the one supplied. This only needs to be done if
  168. * the channel window (and hence this channel object) has stayed open while the user has been
  169. * out of the channel.
  170. *
  171. * @param newChannelInfo The new ChannelInfo object
  172. */
  173. public void setChannelInfo(final ChannelInfo newChannelInfo) {
  174. channelInfo = newChannelInfo;
  175. registerCallbacks();
  176. }
  177. /**
  178. * Called when we join this channel. Just needs to output a message.
  179. */
  180. public void selfJoin() {
  181. isOnChannel = true;
  182. final User me = connection.getLocalUser().get();
  183. getEventBus().publishAsync(new ChannelSelfJoinEvent(this, me));
  184. setIcon("channel");
  185. connection.getInviteManager().removeInvites(channelInfo.getName());
  186. }
  187. /**
  188. * Updates the title of the channel window, and of the main window if appropriate.
  189. */
  190. private void updateTitle() {
  191. final StyledMessageUtils styleUtils = new StyledMessageUtils();
  192. String temp = styleUtils.stripControlCodes(channelInfo.getName());
  193. if (!channelInfo.getTopic().isEmpty()) {
  194. temp += " - " + styleUtils.stripControlCodes(channelInfo.getTopic());
  195. }
  196. setTitle(temp);
  197. }
  198. @Override
  199. public void join() {
  200. connection.getParser().get().joinChannel(channelInfo.getName());
  201. }
  202. @Override
  203. public void part(final String reason) {
  204. channelInfo.part(reason);
  205. resetWindow(PartReason.LOCAL_PART);
  206. }
  207. @Override
  208. public void retrieveListModes() {
  209. channelInfo.requestListModes();
  210. }
  211. /**
  212. * Resets the window state after the client has left a channel.
  213. */
  214. public void resetWindow(final PartReason reason) {
  215. if (isOnChannel) {
  216. partReason = reason;
  217. isOnChannel = false;
  218. }
  219. setIcon("channel-inactive");
  220. // Needs to be published synchronously so that nicklists are cleared before the parser
  221. // is disconnected (which happens synchronously after this method returns).
  222. getEventBus().publish(
  223. new NickListClientsChangedEvent(this, Collections.emptyList()));
  224. }
  225. @Override
  226. public void close() {
  227. super.close();
  228. // Remove any callbacks or listeners
  229. eventHandler.unregisterCallbacks();
  230. connection.getParser().map(Parser::getCallbackManager)
  231. .ifPresent(cm -> cm.unsubscribe(eventHandler));
  232. // Trigger any actions neccessary
  233. if (isOnChannel && connection.getState() != ServerState.CLOSING) {
  234. part(getConfigManager().getOption("general", "partmessage"));
  235. }
  236. // Trigger action for the window closing
  237. getEventBus().publish(new ChannelClosedEvent(this));
  238. }
  239. /**
  240. * Adds a ChannelClient to this Channel.
  241. *
  242. * @param client The client to be added
  243. */
  244. public void addClient(final GroupChatUser client) {
  245. getEventBus().publishAsync(new NickListClientAddedEvent(this, client));
  246. getInputModel().get().getTabCompleter().addEntry(
  247. TabCompletionType.CHANNEL_NICK, client.getNickname());
  248. }
  249. /**
  250. * Removes the specified ChannelClient from this channel.
  251. *
  252. * @param client The client to be removed
  253. */
  254. public void removeClient(final GroupChatUser client) {
  255. getEventBus().publishAsync(new NickListClientRemovedEvent(this, client));
  256. getInputModel().get().getTabCompleter().removeEntry(
  257. TabCompletionType.CHANNEL_NICK, client.getNickname());
  258. if (client.getUser().equals(connection.getLocalUser().orElse(null))) {
  259. resetWindow(PartReason.REMOTE_PART);
  260. }
  261. }
  262. /**
  263. * Replaces the list of known clients on this channel with the specified one.
  264. *
  265. * @param clients The list of clients to use
  266. */
  267. public void setClients(final Collection<GroupChatUser> clients) {
  268. getEventBus().publishAsync(new NickListClientsChangedEvent(this, clients));
  269. getInputModel().get().getTabCompleter().clear(TabCompletionType.CHANNEL_NICK);
  270. getInputModel().get().getTabCompleter().addEntries(
  271. TabCompletionType.CHANNEL_NICK,
  272. clients.stream().map(GroupChatUser::getNickname).collect(Collectors.toList()));
  273. }
  274. /**
  275. * Renames a client that is in this channel.
  276. *
  277. * @param oldName The old nickname of the client
  278. * @param newName The new nickname of the client
  279. */
  280. public void renameClient(final String oldName, final String newName) {
  281. getInputModel().get().getTabCompleter().removeEntry(
  282. TabCompletionType.CHANNEL_NICK, oldName);
  283. getInputModel().get().getTabCompleter().addEntry(
  284. TabCompletionType.CHANNEL_NICK, newName);
  285. refreshClients();
  286. }
  287. @Override
  288. public void refreshClients() {
  289. if (!isOnChannel) {
  290. return;
  291. }
  292. getEventBus().publishAsync(new NickListUpdatedEvent(this));
  293. }
  294. // ---------------------------------------------------- TOPIC HANDLING -----
  295. /**
  296. * Adds the specified topic to this channel's topic list.
  297. *
  298. * @param topic The topic to be added.
  299. */
  300. public void addTopic(final Topic topic) {
  301. synchronized (topics) {
  302. topics.add(topic);
  303. }
  304. updateTitle();
  305. }
  306. @Override
  307. public List<Topic> getTopics() {
  308. synchronized (topics) {
  309. return new ArrayList<>(topics);
  310. }
  311. }
  312. @Override
  313. public Optional<Topic> getCurrentTopic() {
  314. synchronized (topics) {
  315. if (topics.isEmpty()) {
  316. return Optional.empty();
  317. } else {
  318. return Optional.of(getTopics().get(topics.size() - 1));
  319. }
  320. }
  321. }
  322. // ------------------------------------------ PARSER METHOD DELEGATION -----
  323. @Override
  324. public void setTopic(final String topic) {
  325. channelInfo.setTopic(topic);
  326. }
  327. @Override
  328. public int getMaxTopicLength() {
  329. return connection.getParser().get().getMaxTopicLength();
  330. }
  331. @Override
  332. public Optional<Connection> getConnection() {
  333. return Optional.of(connection);
  334. }
  335. @Override
  336. public Optional<GroupChatUser> getUser(final User user) {
  337. final ChannelClientInfo ci = channelInfo.getChannelClient(((Client) user).getClientInfo());
  338. if (ci == null) {
  339. return Optional.empty();
  340. }
  341. return Optional.of(groupChatUserManager.getUserFromClient(ci, user, this));
  342. }
  343. @Override
  344. public Collection<GroupChatUser> getUsers() {
  345. return channelInfo.getChannelClients().stream()
  346. .map(client -> groupChatUserManager.getUserFromClient(client, this))
  347. .collect(Collectors.toList());
  348. }
  349. @Override
  350. public WindowModel getWindowModel() {
  351. return this;
  352. }
  353. @Override
  354. public void kick(final GroupChatUser user, final Optional<String> reason) {
  355. ((ChannelClient) user).getClientInfo().kick(
  356. reason.orElse(getConfigManager().getOption("general", "kickmessage")));
  357. }
  358. @Override
  359. public Collection<ChannelListModeItem> getListModeItems(final char mode) {
  360. return channelInfo.getListMode(mode);
  361. }
  362. @Override
  363. public void setMode(final char mode, @Nullable final String value) {
  364. channelInfo.alterMode(true, mode, value);
  365. }
  366. @Override
  367. public void removeMode(final char mode, final String value) {
  368. channelInfo.alterMode(false, mode, value);
  369. }
  370. @Override
  371. public void flushModes() {
  372. channelInfo.flushModes();
  373. }
  374. @Override
  375. public String getModes() {
  376. return channelInfo.getModes();
  377. }
  378. @Override
  379. public String getModeValue(final char mode) {
  380. return channelInfo.getMode(mode);
  381. }
  382. @Override
  383. public void requestUsersInfo() {
  384. channelInfo.sendWho();
  385. }
  386. }