/* * Copyright (c) 2006-2017 DMDirc Developers * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.dmdirc.addons.dcc; import com.dmdirc.addons.dcc.events.DccChatRequestEvent; import com.dmdirc.addons.dcc.events.DccChatStartingEvent; import com.dmdirc.addons.dcc.events.DccSendRequestEvent; import com.dmdirc.addons.dcc.io.DCC; import com.dmdirc.addons.dcc.io.DCCChat; import com.dmdirc.addons.dcc.io.DCCTransfer; import com.dmdirc.addons.dcc.kde.KFileChooser; import com.dmdirc.addons.dcc.ui.PlaceholderPanel; import com.dmdirc.addons.dcc.ui.TransferPanel; import com.dmdirc.addons.ui_swing.SwingWindowFactory; import com.dmdirc.addons.ui_swing.components.frames.ComponentFrameFactory; import com.dmdirc.addons.ui_swing.components.frames.TextFrame; import com.dmdirc.addons.ui_swing.dialogs.StandardQuestionDialog; import com.dmdirc.addons.ui_swing.injection.MainWindow; import com.dmdirc.commandline.CommandLineOptionsModule.Directory; import com.dmdirc.commandline.CommandLineOptionsModule.DirectoryType; import com.dmdirc.commandparser.parsers.CommandParser; import com.dmdirc.commandparser.parsers.GlobalCommandParser; import com.dmdirc.config.GlobalConfig; import com.dmdirc.config.prefs.PluginPreferencesCategory; import com.dmdirc.config.prefs.PreferencesCategory; import com.dmdirc.config.prefs.PreferencesDialogModel; import com.dmdirc.config.prefs.PreferencesSetting; import com.dmdirc.config.prefs.PreferencesType; import com.dmdirc.events.ClientPrefsOpenedEvent; import com.dmdirc.events.ServerCtcpEvent; import com.dmdirc.interfaces.CommandController; import com.dmdirc.interfaces.Connection; import com.dmdirc.events.eventbus.EventBus; import com.dmdirc.interfaces.User; import com.dmdirc.interfaces.WindowModel; import com.dmdirc.interfaces.config.AggregateConfigProvider; import com.dmdirc.interfaces.config.ConfigProvider; import com.dmdirc.interfaces.config.IdentityController; import com.dmdirc.parser.interfaces.Parser; import com.dmdirc.plugins.PluginDomain; import com.dmdirc.plugins.PluginInfo; import com.dmdirc.ui.WindowManager; import com.dmdirc.ui.input.TabCompleterFactory; import com.dmdirc.ui.messages.BackBufferFactory; import com.google.common.collect.Sets; import java.awt.Dialog; import java.awt.Window; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; import java.util.Set; import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import net.engio.mbassy.listener.Handler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.dmdirc.util.LogUtils.USER_ERROR; /** * This plugin adds DCC to DMDirc. */ @Singleton public class DCCManager { private static final Logger LOG = LoggerFactory.getLogger(DCCManager.class); private final BackBufferFactory backBufferFactory; /** Our DCC Container window. */ private PlaceholderContainer container; /** Config manager to read settings from. */ private final AggregateConfigProvider config; /** Window Management. */ private final WindowManager windowManager; /** The command controller to use. */ private final CommandController commandController; /** The factory to use for tab completers. */ private final TabCompleterFactory tabCompleterFactory; /** The client's main window that will parent any new windows. */ private final Window mainWindow; /** The configuration domain to use. */ private final String domain; /** The bus to dispatch events on. */ private final EventBus eventBus; /** Plugin info. */ private final PluginInfo pluginInfo; /** * Creates a new instance of this plugin. */ @Inject public DCCManager( @MainWindow final Window mainWindow, @PluginDomain(DCCPlugin.class) final PluginInfo pluginInfo, final IdentityController identityController, @GlobalConfig final AggregateConfigProvider globalConfig, final CommandController commandController, final WindowManager windowManager, final TabCompleterFactory tabCompleterFactory, final SwingWindowFactory windowFactory, final ComponentFrameFactory componentFrameFactory, final EventBus eventBus, final GlobalCommandParser commandParser, @Directory(DirectoryType.BASE) final String baseDirectory, final BackBufferFactory backBufferFactory) { this.mainWindow = mainWindow; this.windowManager = windowManager; this.commandController = commandController; this.tabCompleterFactory = tabCompleterFactory; this.pluginInfo = pluginInfo; this.domain = pluginInfo.getDomain(); this.config = globalConfig; this.eventBus = eventBus; this.backBufferFactory = backBufferFactory; windowFactory.registerImplementation(new ComponentFrameWindowProvider( "com.dmdirc.addons.dcc.ui.PlaceholderPanel", componentFrameFactory, commandParser, PlaceholderPanel::new)); windowFactory.registerImplementation(new ComponentFrameWindowProvider( "com.dmdirc.addons.dcc.ui.TransferPanel", componentFrameFactory, commandParser, () -> new TransferPanel(container, eventBus))); final ConfigProvider defaults = identityController.getAddonSettings(); defaults.setOption(domain, "receive.savelocation", baseDirectory + "downloads" + File.separator); } @Handler public void handlePrefsOpened(final ClientPrefsOpenedEvent event) { final PreferencesDialogModel manager = event.getModel(); final PreferencesCategory general = new PluginPreferencesCategory( pluginInfo, "DCC", "", "category-dcc"); final PreferencesCategory firewall = new PluginPreferencesCategory( pluginInfo, "Firewall", ""); final PreferencesCategory sending = new PluginPreferencesCategory( pluginInfo, "Sending", ""); final PreferencesCategory receiving = new PluginPreferencesCategory( pluginInfo, "Receiving", ""); manager.getCategory("Plugins").addSubCategory(general.setInlineAfter()); general.addSubCategory(firewall.setInline()); general.addSubCategory(sending.setInline()); general.addSubCategory(receiving.setInline()); firewall.addSetting( new PreferencesSetting(PreferencesType.TEXT, pluginInfo.getDomain(), "firewall.ip", "Forced IP", "What IP should be sent as our IP (Blank = work it out)", manager.getConfigManager(), manager.getIdentity())); firewall.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(), "firewall.ports.usePortRange", "Use Port Range", "Useful if you have a firewall that only forwards specific " + "ports", manager.getConfigManager(), manager.getIdentity())); firewall.addSetting(new PreferencesSetting(PreferencesType.INTEGER, pluginInfo.getDomain(), "firewall.ports.startPort", "Start Port", "Port to try to listen on first", manager.getConfigManager(), manager.getIdentity())); firewall.addSetting(new PreferencesSetting(PreferencesType.INTEGER, pluginInfo.getDomain(), "firewall.ports.endPort", "End Port", "Port to try to listen on last", manager.getConfigManager(), manager.getIdentity())); receiving.addSetting(new PreferencesSetting(PreferencesType.DIRECTORY, pluginInfo.getDomain(), "receive.savelocation", "Default save location", "Where the save as window defaults to?", manager.getConfigManager(), manager.getIdentity())); sending.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(), "send.reverse", "Reverse DCC", "With reverse DCC, the sender connects rather than " + "listens like normal dcc", manager.getConfigManager(), manager.getIdentity())); sending.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(), "send.forceturbo", "Use Turbo DCC", "Turbo DCC doesn't wait for ack packets. this is " + "faster but not always supported.", manager.getConfigManager(), manager.getIdentity())); receiving.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(), "receive.reverse.sendtoken", "Send token in reverse receive", "If you have problems with reverse dcc receive resume," + " try toggling this.", manager.getConfigManager(), manager.getIdentity())); general.addSetting(new PreferencesSetting(PreferencesType.INTEGER, pluginInfo.getDomain(), "send.blocksize", "Blocksize to use for DCC", "Change the block size for send/receive, this can " + "sometimes speed up transfers.", manager.getConfigManager(), manager.getIdentity())); general.addSetting(new PreferencesSetting(PreferencesType.BOOLEAN, pluginInfo.getDomain(), "general.percentageInTitle", "Show percentage of transfers in the window title", "Show the current percentage of transfers in the DCC window " + "title", manager.getConfigManager(), manager.getIdentity())); } public String getDomain() { return domain; } /** * Ask the location to save a file, then start the download. * * @param nickname Person this dcc is from. * @param send The DCCSend to save for. * @param parser The parser this send was received on * @param reverse Is this a reverse dcc? * @param token Token used in reverse dcc. */ public void saveFile(final String nickname, final DCCTransfer send, final Parser parser, final boolean reverse, final String token) { // New thread to ask the user where to save in to stop us locking the UI new Thread(() -> { final JFileChooser jc = KFileChooser.getFileChooser(config, this, config.getOption(getDomain(), "receive.savelocation")); final int result; if (config.getOptionBool(getDomain(), "receive.autoaccept")) { result = JFileChooser.APPROVE_OPTION; } else { result = showFileChooser(send, jc); } if (result != JFileChooser.APPROVE_OPTION) { return; } send.setFileName(jc.getSelectedFile().getPath()); if (!handleExists(send, jc, nickname, parser, reverse, token)) { return; } final boolean resume = handleResume(jc); if (reverse && !token.isEmpty()) { final TransferContainer container1 = new TransferContainer(this, send, config, backBufferFactory, "*Receive: " + nickname, nickname, null, eventBus); windowManager.addWindow(getContainer(), container1); send.setToken(token); if (resume) { if (config.getOptionBool(getDomain(), "receive.reverse.sendtoken")) { parser.sendCTCP(nickname, "DCC", "RESUME " + send.getShortFileName() + " 0 " + jc.getSelectedFile().length() + ' ' + token); } else { parser.sendCTCP(nickname, "DCC", "RESUME " + send.getShortFileName() + " 0 " + jc.getSelectedFile().length()); } } else { if (listen(send)) { parser.sendCTCP(nickname, "DCC", "SEND " + send.getShortFileName() + ' ' + DCC.ipToLong(getListenIP(parser)) + ' ' + send.getPort() + ' ' + send.getFileSize() + ' ' + token); } } } else { final TransferContainer container1 = new TransferContainer(this, send, config, backBufferFactory, "Receive: " + nickname, nickname, null, eventBus); windowManager.addWindow(getContainer(), container1); if (resume) { parser.sendCTCP(nickname, "DCC", "RESUME " + send.getShortFileName() + ' ' + send.getPort() + ' ' + jc.getSelectedFile().length()); } else { send.connect(); } } }, "saveFileThread: " + send.getShortFileName()).start(); } /** * Checks if the selected file exists and prompts the user as required. * * @param send DCC Transfer * @param jc File chooser * @param nickname Remote nickname * @param parser Parser * @param reverse Reverse DCC? * @param token DCC token * * @return true if the user wants to continue, false if they wish to abort */ private boolean handleExists(final DCCTransfer send, final JFileChooser jc, final String nickname, final Parser parser, final boolean reverse, final String token) { if (jc.getSelectedFile().exists() && send.getFileSize() > -1 && send.getFileSize() <= jc.getSelectedFile().length()) { if (config.getOptionBool(getDomain(), "receive.autoaccept")) { return false; } else { JOptionPane.showMessageDialog( mainWindow, "This file has already " + "been completed, or is longer than the file you are " + "receiving.\nPlease choose a different file.", "Problem with selected file", JOptionPane.ERROR_MESSAGE); saveFile(nickname, send, parser, reverse, token); return false; } } return true; } /** * Prompts the user to resume a transfer if required. * * @param jc File chooser * * @return true if the user wants to continue the transfer false otherwise */ private boolean handleResume(final JFileChooser jc) { if (jc.getSelectedFile().exists()) { if (config.getOptionBool(getDomain(), "receive.autoaccept")) { return true; } else { final int result = JOptionPane.showConfirmDialog( mainWindow, "This file exists already" + ", do you want to resume an exisiting download?", "Resume Download?", JOptionPane.YES_NO_OPTION); return result == JOptionPane.YES_OPTION; } } return false; } /** * Sets up and display a file chooser. * * @param send DCCTransfer object sending the file * @param jc File chooser * * @return the return state of the file chooser on popdown: *