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
21
LICENSE
Normal 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.
|
1
linux/.sync/changelog.txt
Normal file
@ -0,0 +1 @@
|
||||
write your changelog here
|
@ -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
@ -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>
|
||||
|
@ -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(),
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
src/main/java/de/mc8051/arma3launcher/objects/Changelog.java
Normal 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;
|
||||
}
|
||||
}
|
@ -79,6 +79,10 @@ public class ModFile implements AbstractMod {
|
||||
return modfileString;
|
||||
}
|
||||
|
||||
public String getModPath() {
|
||||
return (parent == null ? "" : parent + "/") + modfileString;
|
||||
}
|
||||
|
||||
public File getLocaleFile() {
|
||||
return f;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
|
21
src/main/java/de/mc8051/arma3launcher/utils/ImageUtils.java
Normal 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;
|
||||
}
|
||||
}
|
143
src/main/java/de/mc8051/arma3launcher/utils/TaskBarUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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": "",
|
||||
|
189
src/main/resources/disclaimer.html
Normal 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>
|
BIN
src/main/resources/icons/changelog_16.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
src/main/resources/icons/download_16.png
Normal file
After Width: | Height: | Size: 476 B |
BIN
src/main/resources/icons/github_32.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/main/resources/icons/logo_256.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/icons/logo_32.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
src/main/resources/icons/play_16.png
Normal file
After Width: | Height: | Size: 511 B |
BIN
src/main/resources/icons/preset_16.png
Normal file
After Width: | Height: | Size: 237 B |
BIN
src/main/resources/icons/settings_16.png
Normal file
After Width: | Height: | Size: 566 B |
BIN
src/main/resources/icons/twitter_32.png
Normal file
After Width: | Height: | Size: 912 B |
@ -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
|
||||
@ -97,3 +95,10 @@ resume=Fortsetzen
|
||||
changed_filesize=Veränderte Dateien
|
||||
changed_files=Geänderte Dateien
|
||||
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
|
@ -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
|
||||
@ -95,3 +93,10 @@ 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
|
||||
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
|