wörk wörk (day 3)

* added License file
* added changelog feature
* added zsync user-agent
* added application and taskbar icon
* added taskbar progressbar
* added social buttons
* added about page with disclaimer, copyright and library license
* changed gui design alot
* fixed some gui errors
* fixed generateRepo.sh skip empty files
This commit is contained in:
Niklas 2020-03-28 05:17:50 +01:00
parent 9c0b6bcf83
commit 5eeffde3c3
29 changed files with 2020 additions and 1053 deletions

21
LICENSE Normal file
View File

@ -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.

View File

@ -0,0 +1 @@
write your changelog here

View File

@ -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"

11
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>de.mc8051</groupId>
<artifactId>arma3launcher</artifactId>
<version>1.0-SNAPSHOT</version>
<version>0.1.1000</version>
<repositories>
<repository>
@ -44,7 +44,7 @@
<dependency>
<groupId>com.github.Gurkengewuerz</groupId>
<artifactId>zsyncer</artifactId>
<version>locale_fix-2f7565d392-1</version>
<version>1de0d3f651</version>
</dependency>
</dependencies>
@ -65,6 +65,13 @@
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources/icons</directory>
<filtering>false</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>

View File

@ -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(),

File diff suppressed because it is too large Load Diff

View File

@ -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("<html><a href=''>https://gurkengewuerz.de</a></html>");
aboutProjectLabel.setText("<html><a href=''>"+ArmA3Launcher.config.getString("social.github")+"</a></html>");
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<String> 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) {
}
}
}

View File

@ -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;
}
}

View File

@ -79,6 +79,10 @@ public class ModFile implements AbstractMod {
return modfileString;
}
public String getModPath() {
return (parent == null ? "" : parent + "/") + modfileString;
}
public File getLocaleFile() {
return f;
}

View File

@ -30,6 +30,8 @@ public class FileChecker implements Observable {
private JProgressBar pb;
private boolean stop = false;
private boolean checked = false;
private ArrayList<Path> deleted = new ArrayList<>();
private HashMap<String, ArrayList<ModFile>> 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() {

View File

@ -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;

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -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];

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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": "",

View File

@ -0,0 +1,189 @@
<html>
<head>
</head>
<body>
<h1>Haftungsausschluss</h1>
Gemäß <i>§1 Absatz 2 Satz 3 ProdHaftG</i> 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.<br/><br/>
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.
<br/>
<br/>
<h1>Icons</h1>
<div>
Icons made by <a href="https://www.flaticon.com/authors/freepik" title="Freepik">Freepik</a> from
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</div>
<div>
Icons made by <a href="https://www.flaticon.com/authors/pixel-perfect" title="Pixel perfect">Pixel perfect</a> from
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</div>
<div>
Icons made by <a href="https://www.flaticon.com/authors/roundicons" title="Roundicons">Roundicons</a> from
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
</div>
<br/>
<br/>
<h1>Licenses</h1>
<h3>de.mc8051.arma3launcher</h3>
<tt>
MIT License<br/>
<br/>
Copyright (c) 2020 Niklas Schütrumpf (Gurkengewuerz)<br/>
<br/>
Permission is hereby granted, free of charge, to any person obtaining a copy<br/>
of this software and associated documentation files (the "Software"), to deal<br/>
in the Software without restriction, including without limitation the rights<br/>
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell<br/>
copies of the Software, and to permit persons to whom the Software is<br/>
furnished to do so, subject to the following conditions:<br/>
<br/>
The above copyright notice and this permission notice shall be included in all<br/>
copies or substantial portions of the Software.<br/>
<br/>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR<br/>
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,<br/>
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE<br/>
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER<br/>
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,<br/>
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/>
</tt>
<br/>
<h3>com.formdev.flatlaf</h3>
<a href="">https://github.com/JFormDesigner/FlatLaf</a><br/>
<tt>
Copyright 2019 FormDev Software GmbH<br/>
<br/>
Licensed under the Apache License, Version 2.0 (the "License");<br/>
you may not use this file except in compliance with the License.<br/>
You may obtain a copy of the License at<br/>
<br/>
http://www.apache.org/licenses/LICENSE-2.0<br/>
<br/>
Unless required by applicable law or agreed to in writing, software<br/>
distributed under the License is distributed on an "AS IS" BASIS,<br/>
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br/>
See the License for the specific language governing permissions and<br/>
limitations under the License.<br/>
</tt>
<br/>
<h3>co.bitshfted.xapps.zsync</h3>
<a href="">https://github.com/bitshifted/zsyncer</a><br/>
<tt>
Copyright (c) 2015, Salesforce.com, Inc. All rights reserved.<br/>
Copyright (c) 2020, Bitshift (bitshifted.co), Inc. All rights reserved.<br/>
<br/>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the<br/>
following conditions are met:<br/>
<br/>
Redistributions of source code must retain the above copyright notice, this list of conditions and the following<br/>
disclaimer.<br/>
<br/>
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following<br/>
disclaimer in the documentation and/or other materials provided with the distribution.<br/>
<br/>
Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products<br/>
derived from this software without specific prior written permission.<br/>
<br/>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,<br/>
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE<br/>
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,<br/>
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR<br/>
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,<br/>
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE<br/>
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br/>
</tt>
<br/>
<h3>com.github.RalleYTN.SimpleRegistry</h3>
<a href="">https://github.com/RalleYTN/SimpleRegistry</a><br/>
<tt>
MIT License<br/>
<br/>
Copyright (c) 2017 Ralph Niemitz<br/>
<br/>
Permission is hereby granted, free of charge, to any person obtaining a copy<br/>
of this software and associated documentation files (the "Software"), to deal<br/>
in the Software without restriction, including without limitation the rights<br/>
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell<br/>
copies of the Software, and to permit persons to whom the Software is<br/>
furnished to do so, subject to the following conditions:<br/>
<br/>
The above copyright notice and this permission notice shall be included in all<br/>
copies or substantial portions of the Software.<br/>
<br/>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR<br/>
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,<br/>
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE<br/>
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER<br/>
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,<br/>
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/>
</tt>
<br/>
<h3>com.typesafe.config</h3>
<a href="">https://github.com/lightbend/config</a><br/>
<tt>
Copyright (C) 2011-2012 Typesafe Inc. http://typesafe.com<br/>
<br/>
Licensed under the Apache License, Version 2.0 (the "License");<br/>
you may not use this file except in compliance with the License.<br/>
You may obtain a copy of the License at<br/>
<br/>
http://www.apache.org/licenses/LICENSE-2.0<br/>
<br/>
Unless required by applicable law or agreed to in writing, software<br/>
distributed under the License is distributed on an "AS IS" BASIS,<br/>
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br/>
See the License for the specific language governing permissions and<br/>
limitations under the License.<br/>
</tt>
<br/>
<h3>org.ini4j.ini4j</h3>
<a href="">https://github.com/facebookarchive/ini4j</a><br/>
<tt>
Copyright 2005,2009 Ivan SZKIBA<br/>
<br/>
Licensed under the Apache License, Version 2.0 (the "License");<br/>
you may not use this file except in compliance with the License.<br/>
You may obtain a copy of the License at<br/>
<br/>
http://www.apache.org/licenses/LICENSE-2.0<br/>
<br/>
Unless required by applicable law or agreed to in writing, software<br/>
distributed under the License is distributed on an "AS IS" BASIS,<br/>
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.<br/>
See the License for the specific language governing permissions and<br/>
limitations under the License.<br/>
</tt>
<h3>org.json.json</h3>
<a href="">https://json.org</a><br/>
<tt>
Copyright (c) 2018 JSON.org<br/>
<br/>
Permission is hereby granted, free of charge, to any person obtaining a copy<br/>
of this software and associated documentation files (the "Software"), to deal<br/>
in the Software without restriction, including without limitation the rights<br/>
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell<br/>
copies of the Software, and to permit persons to whom the Software is<br/>
furnished to do so, subject to the following conditions:<br/>
<br/>
The above copyright notice and this permission notice shall be included in all<br/>
copies or substantial portions of the Software.<br/>
<br/>
The Software shall be used for Good, not Evil.<br/>
<br/>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR<br/>
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,<br/>
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE<br/>
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER<br/>
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,<br/>
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/>
</tt>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

View File

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

View File

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