diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b5fbf99 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Niklas Schütrumpf (Gurkengewuerz) + +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. \ No newline at end of file diff --git a/linux/.sync/changelog.txt b/linux/.sync/changelog.txt new file mode 100644 index 0000000..0557e1a --- /dev/null +++ b/linux/.sync/changelog.txt @@ -0,0 +1 @@ +write your changelog here \ No newline at end of file diff --git a/linux/generateRepo.sh b/linux/generateRepo.sh index f42f7cd..f0a62e0 100644 --- a/linux/generateRepo.sh +++ b/linux/generateRepo.sh @@ -92,15 +92,29 @@ while IFS= read -r folder; do FILEFOLDER=$(find "${folder}" -type f ! -path "*.zsync" | sed 's|^./||') while IFS= read -r folderfile; do filebyte=$(wc -c < "${folderfile}") + + if [ $filebyte -eq 0 ]; then + echo "Skipping \"${folderfile}\" because file is empty" + continue + fi + foldersize=$(expr $foldersize + $filebyte) name=$(echo "${folderfile}" | cut -d"/" -f2-) x="\"${name}\":{\"size\": ${filebyte}, \"sha1\": \"${SHASUMS[$folderfile]}\"},${x}" done <<< "$FILEFOLDER" x=$(echo ${x} | rev | cut -c2- | rev) + + if [ $foldersize -eq 0 ]; then + echo "Skipping complete folder \"${$folder}\" because all files are empty" + continue + fi JSONDATA+=( "\"${folder}\": {\"size\":${foldersize},\"content\":{${x}}}" ) else echo "is file" filebyte=$(wc -c < "${folder}") + if [ $filebyte -eq 0 ]; then + continue + fi JSONDATA+=( "\"${folder}\": {\"size\":${filebyte}, \"sha1\": \"${SHASUMS[$folder]}\"}" ) fi done <<< "$FILELIST" diff --git a/pom.xml b/pom.xml index 2b1aefa..5c46cb4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.mc8051 arma3launcher - 1.0-SNAPSHOT + 0.1.1000 @@ -44,7 +44,7 @@ com.github.Gurkengewuerz zsyncer - locale_fix-2f7565d392-1 + 1de0d3f651 @@ -65,6 +65,13 @@ **/* + + src/main/resources/icons + false + + **/* + + diff --git a/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java b/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java index be6430d..e52e9c7 100644 --- a/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java +++ b/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java @@ -1,9 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2020-2020 Niklas Schütrumpf (Gurkengewuerz) + * + * 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 de.mc8051.arma3launcher; import com.formdev.flatlaf.FlatDarkLaf; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import de.mc8051.arma3launcher.steam.SteamTimer; +import de.mc8051.arma3launcher.utils.TaskBarUtils; import org.ini4j.Ini; import javax.swing.*; @@ -28,6 +53,7 @@ public class ArmA3Launcher { public static String VERSION; public static String CLIENT_NAME; public static String APPLICATION_PATH; + public static String USER_AGENT; public static Config config; public static Ini user_config; @@ -39,11 +65,12 @@ public class ArmA3Launcher { final Properties properties = new Properties(); properties.load(ArmA3Launcher.class.getClassLoader().getResourceAsStream("project.properties")); - VERSION = properties.getProperty("version"); APPLICATION_PATH = getAppData() + CLIENT_NAME; + USER_AGENT = config.getString("sync.useragent") + "/" + VERSION; + if (new File(APPLICATION_PATH).mkdirs()) { Logger.getLogger(ArmA3Launcher.class.getName()).log(Level.SEVERE, "Can not create " + APPLICATION_PATH); System.exit(0); @@ -66,6 +93,8 @@ public class ArmA3Launcher { UIManager.setLookAndFeel(new FlatDarkLaf()); JFrame frame = new JFrame(CLIENT_NAME); + TaskBarUtils.getInstance().setWindow(frame); + LauncherGUI gui = new LauncherGUI(); frame.setContentPane(gui.mainPanel); @@ -74,14 +103,18 @@ public class ArmA3Launcher { public void windowClosing(WindowEvent e) { steamTimer.cancel(); steamTimer.purge(); + TaskBarUtils.getInstance().removeTrayIcon(); + gui.exit(); frame.dispose(); } }); - frame.setMinimumSize(new Dimension(1000, 500)); + frame.setMinimumSize(new Dimension(1000, 550)); frame.pack(); + frame.setIconImage(TaskBarUtils.IMAGE_ICON); frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); steamTimer.scheduleAtFixedRate( new SteamTimer(), diff --git a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form index 25f042d..93864aa 100644 --- a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form +++ b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form @@ -3,9 +3,11 @@ - + - + + + @@ -16,24 +18,26 @@ - + - - + + - + + + - + @@ -41,6 +45,7 @@ + @@ -55,26 +60,28 @@ - + + - + + - + @@ -82,10 +89,20 @@ - + + - + + + + + + + + + + @@ -101,7 +118,7 @@ - + @@ -110,7 +127,7 @@ - + @@ -118,7 +135,7 @@ - + @@ -126,7 +143,7 @@ - + @@ -134,7 +151,7 @@ - + @@ -147,18 +164,72 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -260,7 +331,7 @@ - + @@ -268,123 +339,33 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + - + + + - + + + @@ -410,7 +391,9 @@ - + + + @@ -553,7 +536,9 @@ - + + + @@ -576,7 +561,7 @@ - + @@ -592,7 +577,7 @@ - + @@ -600,7 +585,7 @@ - + @@ -616,7 +601,7 @@ - + @@ -624,23 +609,7 @@ - - - - - - - - - - - - - - - - - + @@ -670,7 +639,9 @@ - + + + @@ -693,7 +664,7 @@ - + @@ -704,6 +675,7 @@ + @@ -714,6 +686,7 @@ + @@ -722,7 +695,7 @@ - + @@ -762,6 +735,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -814,7 +935,7 @@ - + @@ -899,7 +1020,7 @@ - + @@ -963,790 +1084,978 @@ - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - + - - - - + + - - - - + + - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + diff --git a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java index 18fe726..f5d67a9 100644 --- a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java +++ b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java @@ -8,10 +8,12 @@ import de.mc8051.arma3launcher.model.PresetTableModel; import de.mc8051.arma3launcher.model.RepositoryTreeNode; import de.mc8051.arma3launcher.model.ServerTableModel; import de.mc8051.arma3launcher.objects.AbstractMod; +import de.mc8051.arma3launcher.objects.Changelog; import de.mc8051.arma3launcher.objects.Mod; import de.mc8051.arma3launcher.objects.ModFile; import de.mc8051.arma3launcher.objects.Modset; import de.mc8051.arma3launcher.objects.Server; +import de.mc8051.arma3launcher.repo.DownloadStatus; import de.mc8051.arma3launcher.repo.FileChecker; import de.mc8051.arma3launcher.repo.RepositoryManger; import de.mc8051.arma3launcher.repo.SyncList; @@ -19,11 +21,12 @@ import de.mc8051.arma3launcher.repo.Syncer; import de.mc8051.arma3launcher.steam.SteamTimer; import de.mc8051.arma3launcher.utils.Callback; import de.mc8051.arma3launcher.utils.Humanize; +import de.mc8051.arma3launcher.utils.ImageUtils; import de.mc8051.arma3launcher.utils.LangUtils; +import de.mc8051.arma3launcher.utils.TaskBarUtils; import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; +import javax.swing.border.EmptyBorder; import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.text.DefaultFormatter; import javax.swing.tree.DefaultMutableTreeNode; @@ -34,14 +37,23 @@ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; +import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.Properties; +import java.util.Scanner; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; @@ -60,7 +72,6 @@ public class LauncherGUI implements Observer { private JLabel steamStatus; private JLabel armaStatus; private JButton presetPanelButton; - private JPanel logo; private JPanel presetsTab; private JButton playPresetButton; private JButton clonePresetButton; @@ -129,17 +140,32 @@ public class LauncherGUI implements Observer { private JLabel syncDeletedFilesLabel; private JLabel syncAddedFilesLabel; private JLabel syncChangedFilesLabel; - private JLabel syncSizeLabel; + public JLabel syncSizeLabel; private JLabel syncChangedFileSizeLabel; - private JLabel syncFileCountLabel; - public JLabel syncDownloadedLabel; + public JLabel syncFileCountLabel; public JLabel syncDownloadSpeedLabel; private JSplitPane splitView; + public JLabel syncStatusLabel; + private JLabel logo; + private JLabel aboutLabel; + private JButton changelogButton; + private JPanel changelogTab; + private JPanel aboutTab; + private JTextArea changelogPane; + private JScrollPane changelogScroll; + private JLabel twitterIcon; + private JLabel githubIcon; + private JTextPane disclaimer; + private JLabel aboutLogo; + private JLabel aboutClient; + private JLabel aboutProjectLabel; + private JLabel aboutDeveloperLabel; + private JLabel aboutCopyrightLabel; private JCheckBoxTree repoTree; private FileChecker fileChecker; private Syncer syncer; - private SyncList lastSynclist; + private SyncList lastSynclist = null; public LauncherGUI() { fileChecker = new FileChecker(syncCheckProgress); @@ -150,6 +176,16 @@ public class LauncherGUI implements Observer { fileChecker.addObserver(this); syncer.addObserver(this); + new Thread(() -> { + RepositoryManger.getInstance().refreshMeta(); + try { + Thread.sleep(750); + } catch (InterruptedException e) { + e.printStackTrace(); + } + RepositoryManger.getInstance().refreshModset(); + }).start(); + updateTreePanel.remove(tree1); repoTree = new JCheckBoxTree(); @@ -184,63 +220,14 @@ public class LauncherGUI implements Observer { updatePanelButton.setMargin(x); playPanelButton.setMargin(x); presetPanelButton.setMargin(x); + changelogButton.setMargin(x); playPresetButton.setMargin(new Insets(10, 10, 10, 10)); - playPanelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tabbedPane1.setSelectedIndex(0); - } - }); - - updatePanelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tabbedPane1.setSelectedIndex(1); - } - }); - - presetPanelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tabbedPane1.setSelectedIndex(2); - } - }); - - settingsPanelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tabbedPane1.setSelectedIndex(3); - } - }); - serverTable.setModel(new ServerTableModel()); - presetList.setModel(new PresetTableModel()); + presetList.setCellRenderer(new PresetListRenderer()); - presetList.addListSelectionListener(new ListSelectionListener() { - - @Override - public void valueChanged(ListSelectionEvent e) { - if (!e.getValueIsAdjusting()) { - PresetTableModel m = (PresetTableModel) presetList.getModel(); - Modset modset = (Modset) m.getElementAt(presetList.getSelectedIndex()); - - if (modset.getType() == Modset.Type.SERVER) { - renamePresetButton.setEnabled(false); - removePresetButtom.setEnabled(false); - } else { - renamePresetButton.setEnabled(true); - removePresetButtom.setEnabled(true); - } - clonePresetButton.setEnabled(true); - - updateModList(modset); - } - } - }); - modList.setCellRenderer(new ModListRenderer()); subtitle.setText( @@ -255,6 +242,49 @@ public class LauncherGUI implements Observer { initSettings(); + logo.setIcon(new ImageIcon(ImageUtils.getScaledImage(TaskBarUtils.IMAGE_LGO, 128, 128))); + aboutLogo.setIcon(new ImageIcon(ImageUtils.getScaledImage(TaskBarUtils.IMAGE_LGO, 128, 128))); + + aboutClient.setText(ArmA3Launcher.config.getString("name") + " v" + ArmA3Launcher.VERSION); + + aboutDeveloperLabel.setText("https://gurkengewuerz.de"); + aboutProjectLabel.setText(""+ArmA3Launcher.config.getString("social.github")+""); + + InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("disclaimer.html"); + if(resourceAsStream != null) { + Scanner s = new Scanner(resourceAsStream).useDelimiter("\\A"); + String result = s.hasNext() ? s.next() : ""; + disclaimer.setText(result); + } + + + aboutCopyrightLabel.setText(aboutCopyrightLabel.getText().replace("{year}", "" + Calendar.getInstance().get(Calendar.YEAR))); + + twitterIcon.setBorder(new EmptyBorder(2,2,2,2)); + githubIcon.setBorder(new EmptyBorder(2,2,2,2)); + + settingScrollPane.getVerticalScrollBar().setUnitIncrement(16); + updateTreeScrolPane.getVerticalScrollBar().setUnitIncrement(16); + splitView.setDividerLocation(-1); + + presetList.addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + PresetTableModel m = (PresetTableModel) presetList.getModel(); + Modset modset = (Modset) m.getElementAt(presetList.getSelectedIndex()); + + if (modset.getType() == Modset.Type.SERVER) { + renamePresetButton.setEnabled(false); + removePresetButtom.setEnabled(false); + } else { + renamePresetButton.setEnabled(true); + removePresetButtom.setEnabled(true); + } + clonePresetButton.setEnabled(true); + + updateModList(modset); + } + }); + settingsResetDefault.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -268,73 +298,106 @@ public class LauncherGUI implements Observer { } }); - settingScrollPane.getVerticalScrollBar().setUnitIncrement(16); - updateTreeScrolPane.getVerticalScrollBar().setUnitIncrement(16); + + collapseAllButton.addActionListener(e -> repoTree.collapseAllNodes()); + playPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(0)); + updatePanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(1)); + changelogButton.addActionListener(e -> { + tabbedPane1.setSelectedIndex(2); + Changelog.refresh(); + }); + presetPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(3)); + settingsPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(4)); refreshRepoButton.addActionListener(e -> RepositoryManger.getInstance().refreshModset()); - expandAllButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - repoTree.expandAllNodes(); - } + expandAllButton.addActionListener(e -> repoTree.expandAllNodes()); + syncDownloadAbortButton.addActionListener(e -> syncer.stop()); + syncCheckAbortButton.addActionListener(e -> fileChecker.stop()); + + + syncCheckButton.addActionListener(e -> { + syncCheckButton.setEnabled(false); + syncCheckAbortButton.setEnabled(true); + syncCheckStatusLabel.setText("Running!"); + new Thread(() -> fileChecker.check()).start(); + + refreshRepoButton.setEnabled(false); + + repoTree.setCheckboxesEnabled(false); + repoTree.setCheckboxesChecked(false); }); - collapseAllButton.addActionListener(new ActionListener() { + + syncDownloadButton.addActionListener(e -> { + if (!fileChecker.isChecked()) return; + syncDownloadButton.setEnabled(false); + syncDownloadAbortButton.setEnabled(true); + syncPauseButton.setEnabled(true); + syncCheckButton.setEnabled(false); + refreshRepoButton.setEnabled(false); + new Thread(() -> syncer.sync(lastSynclist.clone())).start(); + }); + + syncPauseButton.addActionListener(e -> { + syncer.setPaused(!syncer.isPaused()); + syncPauseButton.setEnabled(false); + }); + + twitterIcon.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - repoTree.collapseAllNodes(); + public void mouseEntered(MouseEvent e) { + twitterIcon.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); + } + + @Override + public void mouseExited(MouseEvent e) { + twitterIcon.setBorder(new EmptyBorder(2,2,2,2)); } }); - new Thread(() -> { - RepositoryManger.getInstance().refreshMeta(); - try { - Thread.sleep(750); - } catch (InterruptedException e) { - e.printStackTrace(); - } - RepositoryManger.getInstance().refreshModset(); - }).start(); - - syncCheckButton.addActionListener(new ActionListener() { + githubIcon.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - syncCheckButton.setEnabled(false); - syncCheckAbortButton.setEnabled(true); - syncCheckStatusLabel.setText("Running!"); - new Thread(() -> fileChecker.check()).start(); + public void mouseEntered(MouseEvent e) { + githubIcon.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2)); + } - repoTree.setCheckboxesEnabled(false); - repoTree.setCheckboxesChecked(false); + @Override + public void mouseExited(MouseEvent e) { + githubIcon.setBorder(new EmptyBorder(2,2,2,2)); } }); - syncCheckAbortButton.addActionListener(new ActionListener() { + aboutLabel.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - fileChecker.stop(); + public void mouseClicked(MouseEvent e) { + tabbedPane1.setSelectedIndex(5); } }); - syncDownloadButton.addActionListener(new ActionListener() { + twitterIcon.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - if(lastSynclist == null) return; - new Thread(() -> syncer.sync(lastSynclist.clone())).start(); + public void mouseClicked(MouseEvent e) { + openURL(ArmA3Launcher.config.getString("social.twitter")); } }); - syncDownloadAbortButton.addActionListener(new ActionListener() { + githubIcon.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - syncer.stop(); + public void mouseClicked(MouseEvent e) { + openURL(ArmA3Launcher.config.getString("social.github")); } }); - syncPauseButton.addActionListener(new ActionListener() { + aboutDeveloperLabel.addMouseListener(new MouseAdapter() { @Override - public void actionPerformed(ActionEvent e) { - syncer.setPaused(!syncer.isPaused()); - syncPauseButton.setEnabled(false); + public void mouseClicked(MouseEvent e) { + openURL("https://gurkengewuerz.de"); + } + }); + + aboutProjectLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + openURL(ArmA3Launcher.config.getString("social.github")); } }); } @@ -377,6 +440,7 @@ public class LauncherGUI implements Observer { playPresetButton.setEnabled(false); syncCheckButton.setEnabled(false); refreshRepoButton.setEnabled(false); + syncDownloadButton.setEnabled(false); playButton.setToolTipText(LangUtils.getInstance().getString("arma_running")); playPresetButton.setToolTipText(LangUtils.getInstance().getString("arma_running")); @@ -408,11 +472,15 @@ public class LauncherGUI implements Observer { syncCheckButton.setEnabled(true); refreshRepoButton.setEnabled(true); + syncDownloadButton.setEnabled(fileChecker.isChecked()); + syncCheckButton.setToolTipText(null); refreshRepoButton.setToolTipText(null); } else { - syncCheckButton.setEnabled(true); - refreshRepoButton.setEnabled(true); + syncCheckButton.setEnabled(false); + refreshRepoButton.setEnabled(false); + + syncDownloadButton.setEnabled(false); syncCheckButton.setToolTipText(LangUtils.getInstance().getString("path_not_set")); refreshRepoButton.setToolTipText(LangUtils.getInstance().getString("path_not_set")); @@ -655,7 +723,7 @@ public class LauncherGUI implements Observer { RepositoryTreeNode lastNode = modFolder; ArrayList path = modfile.getPath(); - for (int i = 0; i < path.size() -1; i++) { + for (int i = 0; i < path.size() - 1; i++) { boolean found = false; for (int j = 0; j < lastNode.getChildCount(); j++) { @@ -703,11 +771,11 @@ public class LauncherGUI implements Observer { public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) { lastSynclist = getSyncList(); if (lastSynclist.getSize() != 0) - syncSizeLabel.setText(Humanize.binaryPrefix(lastSynclist.getSize())); - else syncSizeLabel.setText("0.0 B"); + syncSizeLabel.setText("0.0 B/" + Humanize.binaryPrefix(lastSynclist.getSize())); + else syncSizeLabel.setText("0.0 B/0.0 B"); if (lastSynclist.getCount() != 0) { syncDownloadButton.setEnabled(true); - syncFileCountLabel.setText("" + lastSynclist.getCount()); + syncFileCountLabel.setText("0/" + lastSynclist.getCount()); } else { syncDownloadButton.setEnabled(false); syncFileCountLabel.setText(""); @@ -838,6 +906,17 @@ public class LauncherGUI implements Observer { refreshRepoButton.setEnabled(false); break; } + } else if (s.equals(RepositoryManger.Type.CHANGELOG.toString())) { + if (RepositoryManger.getInstance().getStatus(RepositoryManger.Type.CHANGELOG) == DownloadStatus.FINNISHED) { + SwingUtilities.invokeLater(() -> { + changelogPane.setText(Changelog.get()); + changelogPane.setCaretPosition(0); + changelogPane.setLineWrap(true); + changelogPane.setWrapStyleWord(true); + changelogPane.revalidate(); + changelogPane.repaint(); + }); + } } else if (s.equals("fileChecker")) { syncCheckButton.setEnabled(true); syncCheckAbortButton.setEnabled(false); @@ -867,26 +946,70 @@ public class LauncherGUI implements Observer { syncPauseButton.setEnabled(false); repoTree.setCheckboxesChecked(false); + refreshRepoButton.setEnabled(true); syncAddedFilesLabel.setText("" + 0); syncChangedFilesLabel.setText("" + 0); syncDeletedFilesLabel.setText("" + 0); syncChangedFileSizeLabel.setText("0.0 B"); + } else if (s.equals("syncStopped")) { new Thread(() -> fileChecker.check()).start(); + SwingUtilities.invokeLater(() -> { + syncDownloadButton.setEnabled(false); + syncDownloadAbortButton.setEnabled(false); + syncPauseButton.setEnabled(false); + + syncStatusLabel.setText("Sync stopped"); + syncFileProgress.setValue(0); + TaskBarUtils.getInstance().setValue(0); + TaskBarUtils.getInstance().off(); + }); } else if (s.equals("syncComplete")) { new Thread(() -> fileChecker.check()).start(); + SwingUtilities.invokeLater(() -> { + syncDownloadButton.setEnabled(false); + syncDownloadAbortButton.setEnabled(false); + syncPauseButton.setEnabled(false); + + syncStatusLabel.setText("Sync finished"); + syncFileProgress.setValue(0); + syncFileProgress.setString(""); + TaskBarUtils.getInstance().setValue(0); + TaskBarUtils.getInstance().off(); + TaskBarUtils.getInstance().attention(); + TaskBarUtils.getInstance().notification("Sync complete", "", TrayIcon.MessageType.INFO); + }); } else if (s.equals("syncContinue")) { - syncDownloadAbortButton.setEnabled(true); - syncPauseButton.setEnabled(true); - syncPauseButton.setText(LangUtils.getInstance().getString("pause")); - syncDownloadButton.setEnabled(false); + SwingUtilities.invokeLater(() -> { + syncDownloadAbortButton.setEnabled(true); + syncPauseButton.setEnabled(true); + syncPauseButton.setText(LangUtils.getInstance().getString("pause")); + syncDownloadButton.setEnabled(false); + TaskBarUtils.getInstance().normal(); + }); } else if (s.equals("syncPaused")) { - syncDownloadAbortButton.setEnabled(true); - syncPauseButton.setEnabled(true); - syncPauseButton.setText(LangUtils.getInstance().getString("resume")); - syncDownloadButton.setEnabled(false); + SwingUtilities.invokeLater(() -> { + syncDownloadAbortButton.setEnabled(true); + syncPauseButton.setEnabled(true); + syncPauseButton.setText(LangUtils.getInstance().getString("resume")); + syncDownloadButton.setEnabled(false); + syncFileProgress.setValue(0); + TaskBarUtils.getInstance().paused(); + }); + } + } + + public void exit() { + fileChecker.stop(); + syncer.stop(); + } + + public void openURL(String url) { + try { + Desktop.getDesktop().browse(new URL(url).toURI()); + } catch (Exception ignored) { } } } diff --git a/src/main/java/de/mc8051/arma3launcher/objects/Changelog.java b/src/main/java/de/mc8051/arma3launcher/objects/Changelog.java new file mode 100644 index 0000000..c59aaae --- /dev/null +++ b/src/main/java/de/mc8051/arma3launcher/objects/Changelog.java @@ -0,0 +1,29 @@ +package de.mc8051.arma3launcher.objects; + +import de.mc8051.arma3launcher.repo.RepositoryManger; +import de.mc8051.arma3launcher.utils.Callback; + +/** + * Created by gurkengewuerz.de on 27.03.2020. + */ +public class Changelog { + + private static long lastUpdate = 0; + private static String cache = ""; + + + public static void refresh() { + if(cache.isEmpty() || System.currentTimeMillis() - lastUpdate > 5 * 60 * 1000) { // 5 Minuten + RepositoryManger.getInstance().refreshChangelog(); + lastUpdate = System.currentTimeMillis(); + } + } + + public static String get() { + return cache; + } + + public static void setChangelog(String changelog) { + cache = changelog; + } +} diff --git a/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java b/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java index 43338f5..b504616 100644 --- a/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java +++ b/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java @@ -79,6 +79,10 @@ public class ModFile implements AbstractMod { return modfileString; } + public String getModPath() { + return (parent == null ? "" : parent + "/") + modfileString; + } + public File getLocaleFile() { return f; } diff --git a/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java b/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java index 34c3118..1787827 100644 --- a/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java +++ b/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java @@ -30,6 +30,8 @@ public class FileChecker implements Observable { private JProgressBar pb; private boolean stop = false; + private boolean checked = false; + private ArrayList deleted = new ArrayList<>(); private HashMap> changed = new HashMap<>(); int changedCount = 0; @@ -93,6 +95,11 @@ public class FileChecker implements Observable { checkDeleted(); notifyObservers("fileChecker"); + checked = true; + } + + public boolean isChecked() { + return checked; } public void stop() { diff --git a/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java b/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java index df3a077..aa202f8 100644 --- a/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java +++ b/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java @@ -4,6 +4,7 @@ import de.mc8051.arma3launcher.ArmA3Launcher; import de.mc8051.arma3launcher.interfaces.Observable; import de.mc8051.arma3launcher.interfaces.Observer; import de.mc8051.arma3launcher.objects.AbstractMod; +import de.mc8051.arma3launcher.objects.Changelog; import de.mc8051.arma3launcher.objects.Mod; import de.mc8051.arma3launcher.objects.ModFile; import de.mc8051.arma3launcher.objects.Modset; @@ -45,6 +46,7 @@ public class RepositoryManger implements Observable { private RepositoryManger() { statusMap.put(Type.METADATA, DownloadStatus.FINNISHED); statusMap.put(Type.MODSET, DownloadStatus.FINNISHED); + statusMap.put(Type.CHANGELOG, DownloadStatus.FINNISHED); } private void getAsync(String urlS, Callback.HttpCallback callback) { @@ -55,7 +57,7 @@ public class RepositoryManger implements Observable { HttpRequest request = HttpRequest.newBuilder() .uri(url) .GET() - .headers("Content-Type", "text/plain;charset=UTF-8") + .headers("User-Agent", ArmA3Launcher.USER_AGENT) .timeout(Duration.of(3, SECONDS)) .build(); @@ -191,6 +193,26 @@ public class RepositoryManger implements Observable { }); } + public void refreshChangelog() { + statusMap.replace(Type.CHANGELOG, DownloadStatus.RUNNING); + RepositoryManger.getInstance().notifyObservers(Type.CHANGELOG.toString()); + + getAsync(ArmA3Launcher.config.getString("sync.url") + "/.sync/changelog.txt", new Callback.HttpCallback() { + @Override + public void response(Response r) { + if (!r.isSuccessful()) { + statusMap.replace(Type.CHANGELOG, DownloadStatus.ERROR); + RepositoryManger.getInstance().notifyObservers(Type.CHANGELOG.toString()); + return; + } + + statusMap.replace(Type.CHANGELOG, DownloadStatus.FINNISHED); + RepositoryManger.getInstance().notifyObservers(Type.CHANGELOG.toString()); + Changelog.setChangelog(r.getBody()); + } + }); + } + public static RepositoryManger getInstance() { if (instance == null) instance = new RepositoryManger(); return instance; @@ -218,7 +240,8 @@ public class RepositoryManger implements Observable { public enum Type { METADATA("metadata"), - MODSET("modset"); + MODSET("modset"), + CHANGELOG("changelog"); private String modset; diff --git a/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java b/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java index ec66c6a..cf91072 100644 --- a/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java +++ b/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java @@ -10,6 +10,7 @@ import de.mc8051.arma3launcher.interfaces.Observer; import de.mc8051.arma3launcher.objects.AbstractMod; import de.mc8051.arma3launcher.objects.ModFile; import de.mc8051.arma3launcher.utils.Humanize; +import de.mc8051.arma3launcher.utils.TaskBarUtils; import javax.swing.*; import java.io.IOException; @@ -36,17 +37,19 @@ public class Syncer extends ZsyncObserver implements Observable { private SyncList modlist; private boolean currentDownload_failed = false; + private String currentDownload_sizeS; private boolean controlfile_downloaded = false; private int failed = 0; private int success = 0; - long syncSize; - int syncCount; + private long syncSize; + private String syncSizeString; + private int syncCount; - long downloadStarted; - long downloadEnded; - long downloadSize; - long downloadDownloaded; + private long downloadStarted; + private long downloadEnded; + private long downloadSize; + private long downloadDownloaded; private Zsync zsync; private LauncherGUI gui; @@ -69,10 +72,12 @@ public class Syncer extends ZsyncObserver implements Observable { success = 0; syncSize = ml.getSize(); + syncSizeString = Humanize.binaryPrefix(syncSize); syncCount = ml.getCount(); SwingUtilities.invokeLater(() -> { gui.syncDownloadProgress.setMaximum(syncCount); gui.syncDownloadProgress.setValue(0); + TaskBarUtils.getInstance().normal(); }); boolean lastPause = false; @@ -120,11 +125,13 @@ public class Syncer extends ZsyncObserver implements Observable { if (mf != null) { Zsync.Options o = new Zsync.Options(); o.setOutputFile(Paths.get(mf.getLocaleFile().getAbsolutePath())); + o.setUseragent(ArmA3Launcher.USER_AGENT); try { currentDownload = mf; currentDownload_failed = false; controlfile_downloaded = false; + currentDownload_sizeS = Humanize.binaryPrefix(currentDownload.getSize()); zsync.zsync(URI.create(mf.getRemoteFile() + ".zsync"), o, this); } catch (ZsyncException | IllegalArgumentException e) { @@ -178,14 +185,14 @@ public class Syncer extends ZsyncObserver implements Observable { @Override public void zsyncStarted(URI requestedZsyncUri, Zsync.Options options) { super.zsyncStarted(requestedZsyncUri, options); - System.out.println("ZSync started " + options.getOutputFile()); - } - @Override - public void controlFileDownloadingComplete() { - super.controlFileDownloadingComplete(); - System.out.println("controlFileDownloadingComplete"); - controlfile_downloaded = true; + SwingUtilities.invokeLater(() -> { + gui.syncFileProgress.setValue(0); + gui.syncFileProgress.setString("0 %"); + }); + + System.out.println("ZSync started " + options.getOutputFile()); + SwingUtilities.invokeLater(() -> gui.syncStatusLabel.setText(currentDownload.getModPath() + ": Sync started")); } @Override @@ -193,6 +200,7 @@ public class Syncer extends ZsyncObserver implements Observable { super.zsyncFailed(exception); currentDownload_failed = true; System.out.println("Zsync failed " + exception.getMessage()); + SwingUtilities.invokeLater(() -> gui.syncStatusLabel.setText(currentDownload.getModPath() + ": Sync failed")); } @Override @@ -211,13 +219,22 @@ public class Syncer extends ZsyncObserver implements Observable { else success++; final long finalSize = syncSize - modlist.getSize(); - int i = success + failed; - int percentage = (int) ((double)i / (double)Long.valueOf(syncCount).intValue() * 100); + final int i = success + failed; + final int percentage = (int) ((double) i / (double) Long.valueOf(syncCount).intValue() * 100); + final String modPath = currentDownload.getModPath(); SwingUtilities.invokeLater(() -> { gui.syncDownloadProgress.setValue(i); - gui.syncDownloadedLabel.setText(Humanize.binaryPrefix(finalSize) + " " + " (" + failed + " failed)"); - gui.syncDownloadProgress.setString(percentage + "%"); + gui.syncFileCountLabel.setText(i + "/" + syncCount + " (" + failed + " failed)"); + gui.syncSizeLabel.setText(Humanize.binaryPrefix(finalSize) + "/" + syncSizeString); + + if (currentDownload_failed) + gui.syncStatusLabel.setText(modPath + ": Sync failed"); + else + gui.syncStatusLabel.setText(modPath + ": Sync finished"); + + gui.syncDownloadProgress.setString(percentage + " %"); + TaskBarUtils.getInstance().setValue(percentage); }); finnishCurrent(); @@ -227,6 +244,16 @@ public class Syncer extends ZsyncObserver implements Observable { public void controlFileDownloadingStarted(URI uri, long length) { super.controlFileDownloadingStarted(uri, length); System.out.println("controlFileDownloadingStarted " + length); + SwingUtilities.invokeLater(() -> gui.syncStatusLabel.setText(currentDownload.getModPath() + ": Get Header")); + } + + @Override + public void controlFileDownloadingComplete() { + super.controlFileDownloadingComplete(); + System.out.println("controlFileDownloadingComplete"); + controlfile_downloaded = true; + + SwingUtilities.invokeLater(() -> gui.syncStatusLabel.setText(currentDownload.getModPath() + ": Hashing")); } @Override @@ -234,26 +261,23 @@ public class Syncer extends ZsyncObserver implements Observable { super.remoteFileDownloadingStarted(uri, length); System.out.println("remoteFileDownloadingStarted " + length); - SwingUtilities.invokeLater(() -> { - gui.syncFileProgress.setMaximum(Long.valueOf(length).intValue()); - gui.syncFileProgress.setValue(0); - }); - downloadSize = length; downloadDownloaded = 0; downloadStarted = System.nanoTime(); + + SwingUtilities.invokeLater(() -> gui.syncStatusLabel.setText(currentDownload.getModPath() + ": Downloading")); } @Override public void bytesDownloaded(long bytes) { super.bytesDownloaded(bytes); -// System.out.println("Downloaded " + bytes); downloadDownloaded += bytes; - // TODO: Fix file Download Progress if (controlfile_downloaded) { + final int percentage = (int) (((double) downloadDownloaded / (double) downloadSize) * 100); SwingUtilities.invokeLater(() -> { - gui.syncFileProgress.setValue(Long.valueOf(downloadDownloaded).intValue()); + gui.syncFileProgress.setValue(percentage); + gui.syncFileProgress.setString(percentage + " % " + Humanize.binaryPrefix(downloadDownloaded) + "/" + currentDownload_sizeS); }); } } diff --git a/src/main/java/de/mc8051/arma3launcher/utils/Callback.java b/src/main/java/de/mc8051/arma3launcher/utils/Callback.java index 868c297..9ab2801 100644 --- a/src/main/java/de/mc8051/arma3launcher/utils/Callback.java +++ b/src/main/java/de/mc8051/arma3launcher/utils/Callback.java @@ -9,11 +9,15 @@ import java.io.File; */ public class Callback { - public static interface JFileSelectCallback { //declare an interface with the callback methods, so you can use on more than one class and just refer to the interface + public interface JFileSelectCallback { boolean allowSelection(File path); } - public static interface HttpCallback { + public interface HttpCallback { void response(Response r); } + + public interface ChangelogCallback { + void response(String changelog); + } } diff --git a/src/main/java/de/mc8051/arma3launcher/utils/Humanize.java b/src/main/java/de/mc8051/arma3launcher/utils/Humanize.java index 8959561..1c871e9 100644 --- a/src/main/java/de/mc8051/arma3launcher/utils/Humanize.java +++ b/src/main/java/de/mc8051/arma3launcher/utils/Humanize.java @@ -16,7 +16,7 @@ public class Humanize { final long[] dividers = new long[] { T, G, M, K, 1 }; final String[] units = new String[] { "TB", "GB", "MB", "KB", "B" }; if(value < 1) - throw new IllegalArgumentException("Invalid file size: " + value); + return "0.0 B"; String result = null; for(int i = 0; i < dividers.length; i++){ final long divider = dividers[i]; diff --git a/src/main/java/de/mc8051/arma3launcher/utils/ImageUtils.java b/src/main/java/de/mc8051/arma3launcher/utils/ImageUtils.java new file mode 100644 index 0000000..aa1acde --- /dev/null +++ b/src/main/java/de/mc8051/arma3launcher/utils/ImageUtils.java @@ -0,0 +1,21 @@ +package de.mc8051.arma3launcher.utils; + +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * Created by gurkengewuerz.de on 27.03.2020. + */ +public class ImageUtils { + + public static Image getScaledImage(Image srcImg, int w, int h){ + BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = resizedImg.createGraphics(); + + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(srcImg, 0, 0, w, h, null); + g2.dispose(); + + return resizedImg; + } +} diff --git a/src/main/java/de/mc8051/arma3launcher/utils/TaskBarUtils.java b/src/main/java/de/mc8051/arma3launcher/utils/TaskBarUtils.java new file mode 100644 index 0000000..bc5d2d0 --- /dev/null +++ b/src/main/java/de/mc8051/arma3launcher/utils/TaskBarUtils.java @@ -0,0 +1,143 @@ +package de.mc8051.arma3launcher.utils; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by gurkengewuerz.de on 27.03.2020. + */ +public class TaskBarUtils { + + public static BufferedImage IMAGE_ICON = createIcon(); + public static BufferedImage IMAGE_LGO = createLogo(); + + private static TaskBarUtils instance; + + private final boolean isTaskbarSupported; + private final boolean isSystemtraySupported; + private Taskbar taskbar; + private SystemTray tray; + private TrayIcon trayIcon; + private Window w; + + private TaskBarUtils() { + isTaskbarSupported = Taskbar.isTaskbarSupported(); + if (isTaskbarSupported) { + taskbar = Taskbar.getTaskbar(); + } + + isSystemtraySupported = SystemTray.isSupported(); + if (isSystemtraySupported) { + tray = SystemTray.getSystemTray(); + + try { + trayIcon = new TrayIcon(IMAGE_ICON); + trayIcon.setImageAutoSize(true); + tray.add(trayIcon); + trayIcon.addActionListener(e -> { + if (w == null) return; + if(!(w instanceof JFrame)) return; + SwingUtilities.invokeLater(() -> { + JFrame frame = (JFrame) w; + if(frame.getState()!=Frame.NORMAL) { frame.setState(Frame.NORMAL); } + frame.setVisible(true); + frame.setAlwaysOnTop(true); + frame.toFront(); + frame.requestFocus(); + frame.setAlwaysOnTop(false); + frame.repaint(); + }); + }); + } catch (AWTException e) { + Logger.getLogger(TaskBarUtils.class.getName()).log(Level.SEVERE, null, e); + } + } + } + + public static TaskBarUtils getInstance() { + if (instance == null) instance = new TaskBarUtils(); + return instance; + } + + public void error(Window w) { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.PROGRESS_STATE_WINDOW)) return; + taskbar.setWindowProgressState(w, Taskbar.State.ERROR); + } + + public void normal() { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.PROGRESS_STATE_WINDOW)) return; + taskbar.setWindowProgressState(w, Taskbar.State.NORMAL); + } + + public void off() { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.PROGRESS_STATE_WINDOW)) return; + taskbar.setWindowProgressState(w, Taskbar.State.OFF); + } + + public void paused() { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.PROGRESS_STATE_WINDOW)) return; + taskbar.setWindowProgressState(w, Taskbar.State.PAUSED); + } + + public void setValue(int val) { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.PROGRESS_VALUE_WINDOW)) return; + taskbar.setWindowProgressValue(w, val); + } + + public void attention() { + if (w == null) return; + if (!isTaskbarSupported) return; + if (!taskbar.isSupported(Taskbar.Feature.USER_ATTENTION_WINDOW)) return; + taskbar.requestWindowUserAttention(w); + } + + public void notification(String caption, String text, TrayIcon.MessageType type) { + if (!isSystemtraySupported) return; + if (trayIcon == null) return; + + trayIcon.displayMessage(caption, text, type); + } + + public void setWindow(Window w) { + this.w = w; + } + + public void removeTrayIcon() { + if (!isSystemtraySupported) return; + if (trayIcon == null) return; + tray.remove(trayIcon); + } + + static BufferedImage createIcon() { + try { + return ImageIO.read(TaskBarUtils.class.getResourceAsStream("/icons/logo_32.png")); + } catch (IOException e) { + Logger.getLogger(TaskBarUtils.class.getName()).log(Level.SEVERE, null, e); + return null; + } + } + + static BufferedImage createLogo() { + try { + return ImageIO.read(TaskBarUtils.class.getResourceAsStream("/icons/logo_256.png")); + } catch (IOException e) { + Logger.getLogger(TaskBarUtils.class.getName()).log(Level.SEVERE, null, e); + return null; + } + } +} diff --git a/src/main/resources/arma3launcher.json b/src/main/resources/arma3launcher.json index 1492c4f..ce1dec0 100644 --- a/src/main/resources/arma3launcher.json +++ b/src/main/resources/arma3launcher.json @@ -3,8 +3,13 @@ "title": "Welcome! :P", "subtitle": "${name} v${version}", "sync": { + "useragent": "TheTownSyncer", "url": "http://46.4.195.36" }, + "social": { + "twitter": "https://twitter.com/TheTownServer", + "github": "https://github.com/Gurkengewuerz/arma3launcher" + }, "client": { "armaPath": "", "modPath": "", diff --git a/src/main/resources/disclaimer.html b/src/main/resources/disclaimer.html new file mode 100644 index 0000000..9ead04c --- /dev/null +++ b/src/main/resources/disclaimer.html @@ -0,0 +1,189 @@ + + + + + +

Haftungsausschluss

+Gemäß §1 Absatz 2 Satz 3 ProdHaftG haftet das Entwickler-Team nicht für Schäden, +die aus der Nutzung des Clients entstehen, da dieser unentgeltlich angeboten und ehrenamtlich entwickelt wird. Das +Entwickler-Team übernimmt darüber hinaus keine Garantie für die ordnungsgemäße Funktion des Clients.

+Darausfolgt auch, dass es keinen Anspruch darauf gibt, dass der Client auf jedem System funktioniert oder Programmfehler +durch +Updates behoben werden. Wir bemühen uns zwar, ein fehlerfreies Programm anzubieten, bitten aber um Verständnis, dass +nicht jeder Fehler unsererseits behoben werden kann. +
+
+

Icons

+
+ Icons made by Freepik from + www.flaticon.com +
+
+ Icons made by Pixel perfect from + www.flaticon.com +
+
+ Icons made by Roundicons from + www.flaticon.com +
+
+
+

Licenses

+

de.mc8051.arma3launcher

+ + MIT License
+
+ Copyright (c) 2020 Niklas Schütrumpf (Gurkengewuerz)
+
+ 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.
+
+
+

com.formdev.flatlaf

+https://github.com/JFormDesigner/FlatLaf
+ + Copyright 2019 FormDev Software GmbH
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+

co.bitshfted.xapps.zsync

+https://github.com/bitshifted/zsyncer
+ + Copyright (c) 2015, Salesforce.com, Inc. All rights reserved.
+ Copyright (c) 2020, Bitshift (bitshifted.co), Inc. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+ following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+ disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided with the distribution.
+
+ Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+

com.github.RalleYTN.SimpleRegistry

+https://github.com/RalleYTN/SimpleRegistry
+ + MIT License
+
+ Copyright (c) 2017 Ralph Niemitz
+
+ 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.
+
+
+

com.typesafe.config

+https://github.com/lightbend/config
+ + Copyright (C) 2011-2012 Typesafe Inc. http://typesafe.com
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+

org.ini4j.ini4j

+https://github.com/facebookarchive/ini4j
+ + Copyright 2005,2009 Ivan SZKIBA
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+

org.json.json

+https://json.org
+ + Copyright (c) 2018 JSON.org
+
+ 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 shall be used for Good, not Evil.
+
+ 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.
+
+ + \ No newline at end of file diff --git a/src/main/resources/icons/changelog_16.png b/src/main/resources/icons/changelog_16.png new file mode 100644 index 0000000..61cb2ca Binary files /dev/null and b/src/main/resources/icons/changelog_16.png differ diff --git a/src/main/resources/icons/download_16.png b/src/main/resources/icons/download_16.png new file mode 100644 index 0000000..1a4c6f5 Binary files /dev/null and b/src/main/resources/icons/download_16.png differ diff --git a/src/main/resources/icons/github_32.png b/src/main/resources/icons/github_32.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/src/main/resources/icons/github_32.png differ diff --git a/src/main/resources/icons/logo_256.png b/src/main/resources/icons/logo_256.png new file mode 100644 index 0000000..b19df96 Binary files /dev/null and b/src/main/resources/icons/logo_256.png differ diff --git a/src/main/resources/icons/logo_32.png b/src/main/resources/icons/logo_32.png new file mode 100644 index 0000000..446dca9 Binary files /dev/null and b/src/main/resources/icons/logo_32.png differ diff --git a/src/main/resources/icons/play_16.png b/src/main/resources/icons/play_16.png new file mode 100644 index 0000000..308ad1e Binary files /dev/null and b/src/main/resources/icons/play_16.png differ diff --git a/src/main/resources/icons/preset_16.png b/src/main/resources/icons/preset_16.png new file mode 100644 index 0000000..da5fa1b Binary files /dev/null and b/src/main/resources/icons/preset_16.png differ diff --git a/src/main/resources/icons/settings_16.png b/src/main/resources/icons/settings_16.png new file mode 100644 index 0000000..72bac8b Binary files /dev/null and b/src/main/resources/icons/settings_16.png differ diff --git a/src/main/resources/icons/twitter_32.png b/src/main/resources/icons/twitter_32.png new file mode 100644 index 0000000..2670097 Binary files /dev/null and b/src/main/resources/icons/twitter_32.png differ diff --git a/src/main/resources/lang_de_DE.properties b/src/main/resources/lang_de_DE.properties index 3795b5c..3c8144b 100644 --- a/src/main/resources/lang_de_DE.properties +++ b/src/main/resources/lang_de_DE.properties @@ -10,7 +10,6 @@ check_modset=Modset automatisch client_settings=Client Einstellungen clone=Klonen closed=geschlossen -common=Allgemein cpucount_desc=Gibt an, wie viele Kerne der CPU von Arma 3 genutzt werden sollen. crashdiag_desc=Zusätzlich zur RPT wird beim Arma-Absturz ein Log vom Crash in einer binarisierten Datei abgelegt. description=Bezeichnung @@ -56,7 +55,6 @@ rename=Umbennen repository_content=Repository Inhalt reset_default=Auf Standard zurücksetzten running=gestartet -select_all=Alles auswählen select_folder=Ordner auswählen select_mods=Mods auswählen select_server=Server-Auswahl @@ -96,4 +94,11 @@ changed_filesize_tooltip=Dies ist die ungef resume=Fortsetzen changed_filesize=Veränderte Dateien changed_files=Geänderte Dateien -file_count=Dateianzahl \ No newline at end of file +file_count=Dateianzahl +changelog=Changelog +about=Über +follow_on_twitter=Folgt und auf Twitter +star_on_github="Star us" auf GitHub +client_up_to_date=Client ist aktuell +developer_page=Entwickler-Seite +project_page=Projektseite \ No newline at end of file diff --git a/src/main/resources/lang_en_US.properties b/src/main/resources/lang_en_US.properties index 294762d..a430303 100644 --- a/src/main/resources/lang_en_US.properties +++ b/src/main/resources/lang_en_US.properties @@ -10,7 +10,6 @@ check_modset=Check Modset automatically client_settings=Client Settings clone=Clone closed=closed -common=Common cpucount_desc=Specifies how many cores of the CPU should be used by Arma 3. crashdiag_desc=In addition to the RPT, a log of the crash is stored in a binarized file. description=Description @@ -55,7 +54,6 @@ remove=Remove rename=Rename reset_default=Reset to default running=running -select_all=Select All select_folder=Choose folder select_mods=Select mods select_server=Select Server @@ -94,4 +92,11 @@ check_local_addons=Check local files changed_filesize=Changed size changed_files=Changed files changed_filesize_tooltip=This is the approximate maximum file size that will be downloaded -file_count=Number of files \ No newline at end of file +file_count=Number of files +changelog=Changelog +about=About +follow_on_twitter=Follow us on Twitter +star_on_github=Star us on GitHub +client_up_to_date=Client is up to date +developer_page=Developer page +project_page=Project page \ No newline at end of file