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
+
+
+
+
+
+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