diff --git a/src/main/java/net/raphimc/viaproxy/ViaProxy.java b/src/main/java/net/raphimc/viaproxy/ViaProxy.java index 7038afaf..f0c1a050 100644 --- a/src/main/java/net/raphimc/viaproxy/ViaProxy.java +++ b/src/main/java/net/raphimc/viaproxy/ViaProxy.java @@ -17,8 +17,6 @@ */ package net.raphimc.viaproxy; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import io.netty.channel.Channel; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; @@ -36,20 +34,18 @@ import net.raphimc.viaproxy.cli.ConsoleHandler; import net.raphimc.viaproxy.cli.options.Options; import net.raphimc.viaproxy.injection.Java17ToJava8; -import net.raphimc.viaproxy.plugins.PluginManager; -import net.raphimc.viaproxy.protocolhack.ProtocolHack; import net.raphimc.viaproxy.proxy.ProxyConnection; import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer; import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyHandler; import net.raphimc.viaproxy.saves.SaveManager; +import net.raphimc.viaproxy.tasks.AccountRefreshTask; +import net.raphimc.viaproxy.tasks.LoaderTask; +import net.raphimc.viaproxy.tasks.UpdatedCheckTask; import net.raphimc.viaproxy.ui.ViaProxyUI; import net.raphimc.viaproxy.util.logging.Logger; import javax.swing.*; import java.awt.*; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; public class ViaProxy { @@ -58,6 +54,7 @@ public class ViaProxy { public static SaveManager saveManager; public static NetServer currentProxyServer; public static ChannelGroup c2pChannels; + public static ViaProxyUI ui; public static void main(String[] args) throws Throwable { final IClassProvider classProvider = new GuavaClassPathProvider(); @@ -81,60 +78,22 @@ public static void injectedMain(String[] args) throws InterruptedException { setNettyParameters(); MCPipeline.useOptimizedPipeline(); c2pChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); - Thread loaderThread = new Thread(() -> { - ProtocolHack.init(); - PluginManager.loadPlugins(); - }, "ViaProtocolHack-Loader"); - Thread accountRefreshThread = new Thread(() -> { - saveManager.accountsSave.refreshAccounts(); - saveManager.save(); - }, "AccountRefresh"); - Thread updateCheckThread = new Thread(() -> { - if (VERSION.startsWith("$")) return; // Dev env check - try { - URL url = new URL("https://api.github.com/repos/RaphiMC/ViaProxy/releases/latest"); - HttpURLConnection con = (HttpURLConnection) url.openConnection(); - con.setRequestMethod("GET"); - con.setRequestProperty("User-Agent", "ViaProxy/" + VERSION); - con.setConnectTimeout(5000); - con.setReadTimeout(5000); - - InputStream in = con.getInputStream(); - byte[] bytes = new byte[1024]; - int read; - StringBuilder builder = new StringBuilder(); - while ((read = in.read(bytes)) != -1) builder.append(new String(bytes, 0, read)); - con.disconnect(); - - JsonObject object = JsonParser.parseString(builder.toString()).getAsJsonObject(); - String latestVersion = object.get("tag_name").getAsString().substring(1); - if (!VERSION.equals(latestVersion)) { - Logger.LOGGER.warn("You are running an outdated version of ViaProxy! Latest version: " + latestVersion); - if (hasUI) { - SwingUtilities.invokeLater(() -> { - JFrame frontFrame = new JFrame(); - frontFrame.setAlwaysOnTop(true); - JOptionPane.showMessageDialog(frontFrame, "You are running an outdated version of ViaProxy!\nCurrent version: " + VERSION + "\nLatest version: " + latestVersion, "ViaProxy", JOptionPane.WARNING_MESSAGE); - }); - } - } - } catch (Throwable ignored) { - } - }, "UpdateCheck"); + Thread loaderThread = new Thread(new LoaderTask(), "ViaProtocolHack-Loader"); + Thread accountRefreshThread = new Thread(new AccountRefreshTask(saveManager), "AccountRefresh"); + Thread updateCheckThread = new Thread(new UpdatedCheckTask(hasUI), "UpdateCheck"); if (hasUI) { loaderThread.start(); accountRefreshThread.start(); - final ViaProxyUI[] ui = new ViaProxyUI[1]; - SwingUtilities.invokeLater(() -> ui[0] = new ViaProxyUI()); + SwingUtilities.invokeLater(() -> ui = new ViaProxyUI()); updateCheckThread.start(); loaderThread.join(); accountRefreshThread.join(); - while (ui[0] == null) { + while (ui == null) { Logger.LOGGER.info("Waiting for UI to be initialized..."); Thread.sleep(1000); } - ui[0].setReady(); + ui.setReady(); Logger.LOGGER.info("ViaProxy started successfully!"); return; } diff --git a/src/main/java/net/raphimc/viaproxy/saves/SaveManager.java b/src/main/java/net/raphimc/viaproxy/saves/SaveManager.java index 4a4582ac..a16704e1 100644 --- a/src/main/java/net/raphimc/viaproxy/saves/SaveManager.java +++ b/src/main/java/net/raphimc/viaproxy/saves/SaveManager.java @@ -21,6 +21,7 @@ import com.google.gson.JsonObject; import net.lenni0451.reflect.stream.RStream; import net.raphimc.viaproxy.saves.impl.AccountsSave; +import net.raphimc.viaproxy.saves.impl.UISave; import net.raphimc.viaproxy.util.logging.Logger; import java.io.File; @@ -33,6 +34,7 @@ public class SaveManager { private static final Gson GSON = new Gson(); public final AccountsSave accountsSave = new AccountsSave(); + public final UISave uiSave = new UISave(); public SaveManager() { this.load(); diff --git a/src/main/java/net/raphimc/viaproxy/saves/impl/UISave.java b/src/main/java/net/raphimc/viaproxy/saves/impl/UISave.java new file mode 100644 index 00000000..25ac9f97 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/saves/impl/UISave.java @@ -0,0 +1,94 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.saves.impl; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.raphimc.viaproxy.saves.AbstractSave; + +import javax.swing.*; +import java.util.HashMap; +import java.util.Map; + +public class UISave extends AbstractSave { + + private final Map values; + + public UISave() { + super("ui"); + + this.values = new HashMap<>(); + } + + @Override + public void load(JsonElement jsonElement) { + this.values.clear(); + for (Map.Entry entry : jsonElement.getAsJsonObject().entrySet()) this.values.put(entry.getKey(), entry.getValue().getAsString()); + } + + @Override + public JsonElement save() { + JsonObject jsonObject = new JsonObject(); + for (Map.Entry entry : this.values.entrySet()) jsonObject.addProperty(entry.getKey(), entry.getValue()); + return jsonObject; + } + + public void put(final String key, final String value) { + this.values.put(key, value); + } + + public void loadTextField(final String key, final JTextField textField) { + try { + String value = this.values.get(key); + if (value != null) textField.setText(value); + } catch (Throwable ignored) { + } + } + + public void loadComboBox(final String key, final JComboBox comboBox) { + try { + int index = Integer.parseInt(this.values.get(key)); + if (index >= 0 && index < comboBox.getItemCount()) comboBox.setSelectedIndex(index); + } catch (Throwable ignored) { + } + } + + public void loadSpinner(final String key, final JSpinner spinner) { + try { + Integer value = Integer.valueOf(this.values.get(key)); + if (spinner.getModel() instanceof SpinnerNumberModel) { + SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel(); + Comparable minimum = (Comparable) model.getMinimum(); + Comparable maximum = (Comparable) model.getMaximum(); + if (minimum.compareTo(value) <= 0 && maximum.compareTo(value) >= 0) spinner.setValue(value); + } else { + spinner.setValue(value); + } + } catch (Throwable ignored) { + } + } + + public void loadCheckBox(final String key, final JCheckBox checkBox) { + try { + boolean value = Boolean.parseBoolean(this.values.get(key)); + checkBox.setSelected(value); + } catch (Throwable ignored) { + } + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/tasks/AccountRefreshTask.java b/src/main/java/net/raphimc/viaproxy/tasks/AccountRefreshTask.java new file mode 100644 index 00000000..6fbedf10 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/tasks/AccountRefreshTask.java @@ -0,0 +1,36 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.tasks; + +import net.raphimc.viaproxy.saves.SaveManager; + +public class AccountRefreshTask implements Runnable { + + private final SaveManager saveManager; + + public AccountRefreshTask(final SaveManager saveManager) { + this.saveManager = saveManager; + } + + @Override + public void run() { + this.saveManager.accountsSave.refreshAccounts(); + this.saveManager.save(); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/tasks/LoaderTask.java b/src/main/java/net/raphimc/viaproxy/tasks/LoaderTask.java new file mode 100644 index 00000000..8aa320bb --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/tasks/LoaderTask.java @@ -0,0 +1,31 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.tasks; + +import net.raphimc.viaproxy.plugins.PluginManager; +import net.raphimc.viaproxy.protocolhack.ProtocolHack; + +public class LoaderTask implements Runnable { + + @Override + public void run() { + ProtocolHack.init(); + PluginManager.loadPlugins(); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/tasks/UpdatedCheckTask.java b/src/main/java/net/raphimc/viaproxy/tasks/UpdatedCheckTask.java new file mode 100644 index 00000000..8a39854b --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/tasks/UpdatedCheckTask.java @@ -0,0 +1,117 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.tasks; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.raphimc.viaproxy.ViaProxy; +import net.raphimc.viaproxy.ui.popups.DownloadPopup; +import net.raphimc.viaproxy.util.logging.Logger; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import static net.raphimc.viaproxy.ViaProxy.VERSION; + +public class UpdatedCheckTask implements Runnable { + + private final boolean hasUI; + + public UpdatedCheckTask(final boolean hasUI) { + this.hasUI = hasUI; + } + + @Override + public void run() { + if (VERSION.startsWith("$")) return; // Dev env check + try { + URL url = new URL("https://api.github.com/repos/RaphiMC/ViaProxy/releases/latest"); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", "ViaProxy/" + VERSION); + con.setConnectTimeout(5000); + con.setReadTimeout(5000); + + InputStream in = con.getInputStream(); + byte[] bytes = new byte[1024]; + int read; + StringBuilder builder = new StringBuilder(); + while ((read = in.read(bytes)) != -1) builder.append(new String(bytes, 0, read)); + con.disconnect(); + + JsonObject object = JsonParser.parseString(builder.toString()).getAsJsonObject(); + String latestVersion = object.get("tag_name").getAsString().substring(1); + if (!VERSION.equals(latestVersion)) { + Logger.LOGGER.warn("You are running an outdated version of ViaProxy! Latest version: " + latestVersion); + if (this.hasUI) { + JsonArray assets = object.getAsJsonArray("assets"); + boolean found = false; + for (JsonElement asset : assets) { + JsonObject assetObject = asset.getAsJsonObject(); + if (isViaProxyJar(object, assetObject)) { + found = true; + SwingUtilities.invokeLater(() -> this.showUpdateQuestion(assetObject.get("name").getAsString(), assetObject.get("browser_download_url").getAsString(), latestVersion)); + break; + } + } + if (!found) SwingUtilities.invokeLater(() -> this.showUpdateWarning(latestVersion)); + } + } + } catch (Throwable ignored) { + } + } + + private void showUpdateWarning(final String latestVersion) { + JOptionPane.showMessageDialog(ViaProxy.ui, "You are running an outdated version of ViaProxy!\nCurrent version: " + VERSION + "\nLatest version: " + latestVersion, "ViaProxy", JOptionPane.WARNING_MESSAGE); + } + + private void showUpdateQuestion(final String name, final String downloadUrl, final String latestVersion) { + int chosen = JOptionPane.showConfirmDialog(ViaProxy.ui, "You are running an outdated version of ViaProxy!\nCurrent version: " + VERSION + "\nLatest version: " + latestVersion + "\n\nDo you want to update?", "ViaProxy", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (chosen == JOptionPane.YES_OPTION) { + File f = new File(name); + new DownloadPopup(ViaProxy.ui, downloadUrl, f, () -> { + SwingUtilities.invokeLater(() -> { + JOptionPane.showMessageDialog(ViaProxy.ui, "Downloaded the latest version of ViaProxy!\nPress OK to restart.", "ViaProxy", JOptionPane.INFORMATION_MESSAGE); + try { + Runtime.getRuntime().exec(new String[]{System.getProperty("java.home") + "/bin/java", "-jar", f.getAbsolutePath()}); + System.exit(0); + } catch (IOException e) { + Logger.LOGGER.error("Could not start the new ViaProxy jar", e); + ViaProxy.ui.showException(e); + } + }); + }, t -> { + if (t != null) { + Logger.LOGGER.error("Could not download the latest version of ViaProxy", t); + ViaProxy.ui.showException(t); + } + }); + } + } + + private boolean isViaProxyJar(final JsonObject root, final JsonObject assetObject) { + return assetObject.get("name").getAsString().equals(root.get("name").getAsString() + ".jar"); + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/ui/AUITab.java b/src/main/java/net/raphimc/viaproxy/ui/AUITab.java index f26c794e..4dbfdad9 100644 --- a/src/main/java/net/raphimc/viaproxy/ui/AUITab.java +++ b/src/main/java/net/raphimc/viaproxy/ui/AUITab.java @@ -43,4 +43,7 @@ public void add(final JTabbedPane tabbedPane) { public void setReady() { } + public void onClose() { + } + } diff --git a/src/main/java/net/raphimc/viaproxy/ui/ViaProxyUI.java b/src/main/java/net/raphimc/viaproxy/ui/ViaProxyUI.java index 1d3e943f..ee8d4e00 100644 --- a/src/main/java/net/raphimc/viaproxy/ui/ViaProxyUI.java +++ b/src/main/java/net/raphimc/viaproxy/ui/ViaProxyUI.java @@ -26,6 +26,8 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -67,6 +69,12 @@ private void initWindow() { this.setTitle("ViaProxy v" + ViaProxy.VERSION); this.setIconImage(this.icon.getImage()); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + for (AUITab tab : tabs) tab.onClose(); + } + }); this.setSize(500, 403); this.setResizable(false); this.setLocationRelativeTo(null); diff --git a/src/main/java/net/raphimc/viaproxy/ui/impl/GeneralTab.java b/src/main/java/net/raphimc/viaproxy/ui/impl/GeneralTab.java index 05a9369a..fe778c17 100644 --- a/src/main/java/net/raphimc/viaproxy/ui/impl/GeneralTab.java +++ b/src/main/java/net/raphimc/viaproxy/ui/impl/GeneralTab.java @@ -21,6 +21,7 @@ import net.raphimc.viaprotocolhack.util.VersionEnum; import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.cli.options.Options; +import net.raphimc.viaproxy.saves.impl.UISave; import net.raphimc.viaproxy.ui.AUITab; import net.raphimc.viaproxy.ui.ViaProxyUI; import net.raphimc.viaproxy.util.logging.Logger; @@ -77,6 +78,7 @@ public void mouseReleased(MouseEvent e) { this.serverAddress = new JTextField(); this.serverAddress.setBounds(10, 70, 465, 20); + ViaProxy.saveManager.uiSave.loadTextField("server_address", this.serverAddress); contentPane.add(this.serverAddress); } { @@ -96,6 +98,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } }); + ViaProxy.saveManager.uiSave.loadComboBox("server_version", this.serverVersion); contentPane.add(this.serverVersion); } { @@ -105,6 +108,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i this.bindPort = new JSpinner(new SpinnerNumberModel(25568, 1, 65535, 1)); this.bindPort.setBounds(10, 170, 465, 20); + ViaProxy.saveManager.uiSave.loadSpinner("bind_port", this.bindPort); contentPane.add(this.bindPort); } { @@ -114,17 +118,20 @@ public Component getListCellRendererComponent(JList list, Object value, int i this.authMethod = new JComboBox<>(new String[]{"Use no account", "Use selected account", "Use OpenAuthMod"}); this.authMethod.setBounds(10, 220, 465, 20); + ViaProxy.saveManager.uiSave.loadComboBox("auth_method", this.authMethod); contentPane.add(this.authMethod); } { this.betaCraftAuth = new JCheckBox("BetaCraft Auth (Classic)"); this.betaCraftAuth.setBounds(10, 250, 150, 20); + ViaProxy.saveManager.uiSave.loadCheckBox("betacraft_auth", this.betaCraftAuth); contentPane.add(this.betaCraftAuth); } { this.proxyOnlineMode = new JCheckBox("Proxy Online Mode"); this.proxyOnlineMode.setBounds(350, 250, 465, 20); this.proxyOnlineMode.setToolTipText("Enabling Proxy Online Mode requires your client to have a valid account.\nProxy Online Mode allows your client to see skins on online mode servers and use the signed chat features."); + ViaProxy.saveManager.uiSave.loadCheckBox("proxy_online_mode", this.proxyOnlineMode); contentPane.add(this.proxyOnlineMode); } { @@ -145,6 +152,26 @@ public Component getListCellRendererComponent(JList list, Object value, int i } } + @Override + public void setReady() { + SwingUtilities.invokeLater(() -> { + this.stateButton.setText("Start"); + this.stateButton.setEnabled(true); + }); + } + + @Override + public void onClose() { + UISave save = ViaProxy.saveManager.uiSave; + save.put("server_address", this.serverAddress.getText()); + save.put("server_version", String.valueOf(this.serverVersion.getSelectedIndex())); + save.put("bind_port", String.valueOf(this.bindPort.getValue())); + save.put("auth_method", String.valueOf(this.authMethod.getSelectedIndex())); + save.put("betacraft_auth", String.valueOf(this.betaCraftAuth.isSelected())); + save.put("proxy_online_mode", String.valueOf(this.proxyOnlineMode.isSelected())); + ViaProxy.saveManager.save(); + } + private void setComponentsEnabled(final boolean state) { this.serverAddress.setEnabled(state); this.serverVersion.setEnabled(state); @@ -233,12 +260,4 @@ private void stop() { this.setComponentsEnabled(true); } - @Override - public void setReady() { - SwingUtilities.invokeLater(() -> { - this.stateButton.setText("Start"); - this.stateButton.setEnabled(true); - }); - } - } diff --git a/src/main/java/net/raphimc/viaproxy/ui/popups/DownloadPopup.java b/src/main/java/net/raphimc/viaproxy/ui/popups/DownloadPopup.java new file mode 100644 index 00000000..1034f500 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/ui/popups/DownloadPopup.java @@ -0,0 +1,145 @@ +/* + * This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.viaproxy.ui.popups; + +import net.raphimc.viaproxy.ViaProxy; +import net.raphimc.viaproxy.ui.ViaProxyUI; + +import javax.swing.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.function.Consumer; + +public class DownloadPopup extends JDialog { + + private final ViaProxyUI parent; + private final String url; + private final File file; + private final Runnable finishListener; + private final Consumer stopConsumer; + + private JProgressBar progressBar; + private Thread downloadThread; + + public DownloadPopup(final ViaProxyUI parent, final String url, final File file, final Runnable finishListener, final Consumer stopConsumer) { + super(parent, true); + this.parent = parent; + this.url = url; + this.file = file; + this.finishListener = finishListener; + this.stopConsumer = stopConsumer; + + this.initWindow(); + this.initComponents(); + this.setVisible(true); + } + + private void initWindow() { + this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + this.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + DownloadPopup.this.close(false); + } + }); + this.setTitle("Downloading..."); + this.setSize(400, 110); + this.setResizable(false); + this.setLocationRelativeTo(this.parent); + } + + private void initComponents() { + JPanel contentPane = new JPanel(); + contentPane.setLayout(null); + this.setContentPane(contentPane); + { + this.progressBar = new JProgressBar(); + this.progressBar.setBounds(10, 10, 365, 20); + this.progressBar.setStringPainted(true); + contentPane.add(this.progressBar); + } + { + JButton cancelButton = new JButton("Cancel"); + cancelButton.setBounds(10, 40, 365, 20); + cancelButton.addActionListener(e -> this.close(false)); + contentPane.add(cancelButton); + } + this.start(); + } + + private void start() { + this.downloadThread = new Thread(() -> { + try { + HttpURLConnection con = (HttpURLConnection) new URL(this.url).openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", "Viaproxy/" + ViaProxy.VERSION); + con.setConnectTimeout(5000); + con.setReadTimeout(5000); + + int contentLength = con.getContentLength(); + int current = 0; + File tempFile = File.createTempFile("ViaProxy-download", ""); + InputStream is = con.getInputStream(); + FileOutputStream fos = new FileOutputStream(tempFile); + byte[] buffer = new byte[1024 * 1024]; + int len; + while ((len = is.read(buffer)) != -1) { + if (this.downloadThread.isInterrupted()) throw new InterruptedException(); + fos.write(buffer, 0, len); + + if (contentLength != -1) { + current += len; + this.progressBar.setValue((int) (100F / contentLength * current)); + } + } + fos.close(); + is.close(); + con.disconnect(); + + Files.move(tempFile.toPath(), this.file.toPath(), StandardCopyOption.REPLACE_EXISTING); + + this.close(true); + this.finishListener.run(); + } catch (InterruptedException ignored) { + } catch (Throwable t) { + this.close(false); + this.stopConsumer.accept(t); + } + }, "Download Popup Thread"); + this.downloadThread.setDaemon(true); + this.downloadThread.start(); + } + + private void close(final boolean success) { + if (!success && this.downloadThread != null && this.downloadThread.isAlive()) { + this.downloadThread.interrupt(); + this.stopConsumer.accept(null); + } + + this.setVisible(false); + this.dispose(); + } + +}