wörk wörk (day 2)
This commit is contained in:
parent
2decbf015c
commit
ed51a990a9
16
pom.xml
16
pom.xml
@ -31,6 +31,11 @@
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.14</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.formdev</groupId>
|
||||
<artifactId>flatlaf</artifactId>
|
||||
@ -47,15 +52,14 @@
|
||||
<version>0.5.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.takari.zsync</groupId>
|
||||
<artifactId>zsync-parent</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<type>pom</type>
|
||||
<groupId>com.github.mfornos</groupId>
|
||||
<artifactId>humanize-slim</artifactId>
|
||||
<version>1.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.bitshifted</groupId>
|
||||
<groupId>com.github.Gurkengewuerz</groupId>
|
||||
<artifactId>zsyncer</artifactId>
|
||||
<version>-f69d844481-1</version>
|
||||
<version>locale_fix-2f7565d392-1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="1048" height="526"/>
|
||||
<xy x="20" y="20" width="1048" height="556"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@ -407,7 +407,7 @@
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="3b45c" layout-manager="GridLayoutManager" row-count="6" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="3b45c" layout-manager="GridLayoutManager" row-count="6" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="3" bottom="0" right="0"/>
|
||||
<constraints border-constraint="Center"/>
|
||||
<properties/>
|
||||
@ -423,7 +423,7 @@
|
||||
</component>
|
||||
<hspacer id="f47e0">
|
||||
<constraints>
|
||||
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
<grid row="0" column="2" row-span="1" col-span="2" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<component id="7b453" class="javax.swing.JLabel" binding="syncCheckStatusLabel">
|
||||
@ -437,7 +437,7 @@
|
||||
</component>
|
||||
<component id="c7b6" class="javax.swing.JProgressBar" binding="syncCheckProgress">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
<grid row="1" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<string value=""/>
|
||||
@ -448,7 +448,7 @@
|
||||
<grid id="c1d6e" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="2" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@ -526,6 +526,23 @@
|
||||
<text value="0"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="4c8b" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="changed_filesize"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="585a0" class="javax.swing.JLabel" binding="syncChangedFileSizeLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="0.0 B"/>
|
||||
<toolTipText resource-bundle="lang" key="changed_filesize_tooltip"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
@ -573,22 +590,9 @@
|
||||
<text resource-bundle="lang" key="total_file_size"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="54eb4">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="d85a8" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="downloaded"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f5997" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="speed"/>
|
||||
@ -596,7 +600,7 @@
|
||||
</component>
|
||||
<component id="25551" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="remaining_time"/>
|
||||
@ -610,17 +614,9 @@
|
||||
<text value="0.0 B"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d9013" class="javax.swing.JLabel">
|
||||
<component id="617c7" class="javax.swing.JLabel" binding="syncDownloadSpeedLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="617c7" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
@ -628,7 +624,39 @@
|
||||
</component>
|
||||
<component id="b64ac" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d85a8" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="downloaded"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="d9013" class="javax.swing.JLabel" binding="syncDownloadedLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="28be3" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="lang" key="file_count"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="9148" class="javax.swing.JLabel" binding="syncFileCountLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value=""/>
|
||||
@ -671,24 +699,24 @@
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="f39cc" class="javax.swing.JProgressBar" binding="progressBar2" default-binding="true">
|
||||
<component id="f39cc" class="javax.swing.JProgressBar" binding="syncDownloadProgress">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<string value=""/>
|
||||
<stringPainted value="true"/>
|
||||
<value value="50"/>
|
||||
<value value="0"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="87392" class="javax.swing.JProgressBar" binding="progressBar3" default-binding="true">
|
||||
<component id="87392" class="javax.swing.JProgressBar" binding="syncFileProgress">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<string value=""/>
|
||||
<stringPainted value="true"/>
|
||||
<value value="50"/>
|
||||
<value value="0"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="99173" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
|
@ -5,6 +5,7 @@ import de.mc8051.arma3launcher.model.JCheckBoxTree;
|
||||
import de.mc8051.arma3launcher.model.ModListRenderer;
|
||||
import de.mc8051.arma3launcher.model.PresetListRenderer;
|
||||
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.Mod;
|
||||
@ -13,9 +14,12 @@ import de.mc8051.arma3launcher.objects.Modset;
|
||||
import de.mc8051.arma3launcher.objects.Server;
|
||||
import de.mc8051.arma3launcher.repo.FileChecker;
|
||||
import de.mc8051.arma3launcher.repo.RepositoryManger;
|
||||
import de.mc8051.arma3launcher.repo.SyncList;
|
||||
import de.mc8051.arma3launcher.repo.Syncer;
|
||||
import de.mc8051.arma3launcher.steam.SteamTimer;
|
||||
import de.mc8051.arma3launcher.utils.Callback;
|
||||
import de.mc8051.arma3launcher.utils.LangUtils;
|
||||
import humanize.Humanize;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
@ -24,6 +28,7 @@ import javax.swing.plaf.basic.BasicTabbedPaneUI;
|
||||
import javax.swing.text.DefaultFormatter;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.DefaultTreeModel;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
@ -37,11 +42,6 @@ import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
@ -115,8 +115,8 @@ public class LauncherGUI implements Observer {
|
||||
private JProgressBar syncCheckProgress;
|
||||
private JButton syncCheckAbortButton;
|
||||
private JButton syncCheckButton;
|
||||
private JProgressBar progressBar2;
|
||||
private JProgressBar progressBar3;
|
||||
public JProgressBar syncDownloadProgress;
|
||||
public JProgressBar syncFileProgress;
|
||||
private JButton syncDownloadButton;
|
||||
private JButton syncDownloadAbortButton;
|
||||
private JButton syncPauseButton;
|
||||
@ -130,29 +130,33 @@ public class LauncherGUI implements Observer {
|
||||
private JLabel syncAddedFilesLabel;
|
||||
private JLabel syncChangedFilesLabel;
|
||||
private JLabel syncSizeLabel;
|
||||
private JLabel syncChangedFileSizeLabel;
|
||||
private JLabel syncFileCountLabel;
|
||||
public JLabel syncDownloadedLabel;
|
||||
public JLabel syncDownloadSpeedLabel;
|
||||
|
||||
private JCheckBoxTree repoTree;
|
||||
private FileChecker fileChecker;
|
||||
|
||||
// TODO: Updater
|
||||
/*
|
||||
Prüfung
|
||||
In eine Liste hinzufügen wenn Datei in modset.json (Neu runterladen), nicht in modset.json (zum Löschen) oder die Größe unterschiedlich ist (Geändert)
|
||||
Checkboxen beim Syncronisieren deaktivieren
|
||||
*/
|
||||
private Syncer syncer;
|
||||
private SyncList lastSynclist;
|
||||
|
||||
public LauncherGUI() {
|
||||
fileChecker = new FileChecker(syncCheckProgress);
|
||||
syncer = new Syncer(this);
|
||||
|
||||
RepositoryManger.getInstance().addObserver(this);
|
||||
SteamTimer.addObserver(this);
|
||||
fileChecker.addObserver(this);
|
||||
syncer.addObserver(this);
|
||||
|
||||
updateTreePanel.remove(tree1);
|
||||
|
||||
repoTree = new JCheckBoxTree();
|
||||
updateTreePanel.add(repoTree, BorderLayout.CENTER);
|
||||
|
||||
DefaultTreeModel model = (DefaultTreeModel) repoTree.getModel();
|
||||
model.setRoot(new RepositoryTreeNode("Repository"));
|
||||
|
||||
updateTreePanel.revalidate();
|
||||
updateTreePanel.repaint();
|
||||
|
||||
@ -221,7 +225,6 @@ public class LauncherGUI implements Observer {
|
||||
if (!e.getValueIsAdjusting()) {
|
||||
PresetTableModel m = (PresetTableModel) presetList.getModel();
|
||||
Modset modset = (Modset) m.getElementAt(presetList.getSelectedIndex());
|
||||
System.out.println(modset.getName());
|
||||
|
||||
if (modset.getType() == Modset.Type.SERVER) {
|
||||
renamePresetButton.setEnabled(false);
|
||||
@ -283,6 +286,11 @@ public class LauncherGUI implements Observer {
|
||||
|
||||
new Thread(() -> {
|
||||
RepositoryManger.getInstance().refreshMeta();
|
||||
try {
|
||||
Thread.sleep(750);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
RepositoryManger.getInstance().refreshModset();
|
||||
}).start();
|
||||
|
||||
@ -294,7 +302,8 @@ public class LauncherGUI implements Observer {
|
||||
syncCheckStatusLabel.setText("Running!");
|
||||
new Thread(() -> fileChecker.check()).start();
|
||||
|
||||
// TODO: disable JTree Checkboxes
|
||||
repoTree.setCheckboxesEnabled(false);
|
||||
repoTree.setCheckboxesChecked(false);
|
||||
}
|
||||
});
|
||||
|
||||
@ -304,6 +313,28 @@ public class LauncherGUI implements Observer {
|
||||
fileChecker.stop();
|
||||
}
|
||||
});
|
||||
|
||||
syncDownloadButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new Thread(() -> syncer.sync(lastSynclist.clone())).start();
|
||||
}
|
||||
});
|
||||
|
||||
syncDownloadAbortButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
syncer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
syncPauseButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
syncer.setPaused(!syncer.isPaused());
|
||||
syncPauseButton.setEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void infoBox(String infoMessage, String titleBar) {
|
||||
@ -440,7 +471,7 @@ public class LauncherGUI implements Observer {
|
||||
}
|
||||
|
||||
String modPath = ArmA3Launcher.user_config.get("client", "modPath");
|
||||
if(sPath.equalsIgnoreCase(modPath)) {
|
||||
if (sPath.equalsIgnoreCase(modPath)) {
|
||||
SwingUtilities.invokeLater(() -> errorBox(LangUtils.getInstance().getString("same_mod_arma_dir_msg"), LangUtils.getInstance().getString("same_mod_arma_dir")));
|
||||
return false;
|
||||
}
|
||||
@ -457,7 +488,7 @@ public class LauncherGUI implements Observer {
|
||||
String sPath = path.getAbsolutePath();
|
||||
|
||||
String armaPath = ArmA3Launcher.user_config.get("client", "armaPath");
|
||||
if(sPath.equalsIgnoreCase(armaPath)) {
|
||||
if (sPath.equalsIgnoreCase(armaPath)) {
|
||||
SwingUtilities.invokeLater(() -> errorBox(LangUtils.getInstance().getString("same_mod_arma_dir_msg"), LangUtils.getInstance().getString("same_mod_arma_dir")));
|
||||
return false;
|
||||
}
|
||||
@ -550,78 +581,54 @@ public class LauncherGUI implements Observer {
|
||||
spinner.addChangeListener(new SettingsHandler.SpinnerListener(paraObj));
|
||||
}
|
||||
|
||||
public ArrayList<AbstractMod> getSyncList() {
|
||||
ArrayList<AbstractMod> modList = new ArrayList<>();
|
||||
public SyncList getSyncList() {
|
||||
SyncList synclist = new SyncList();
|
||||
|
||||
HashMap<String, ArrayList<String>> tempMap = new HashMap<>();
|
||||
for (TreePath checkedPath : repoTree.getCheckedPaths()) {
|
||||
DefaultMutableTreeNode tn = (DefaultMutableTreeNode)checkedPath.getLastPathComponent();
|
||||
DefaultTreeModel model = (DefaultTreeModel) repoTree.getModel();
|
||||
RepositoryTreeNode root = (RepositoryTreeNode) model.getRoot();
|
||||
for (TreeNode leaf : root.getAllLeafNodes()) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) leaf;
|
||||
TreeNode[] path = node.getPath();
|
||||
boolean isSelected = repoTree.isSelected(new TreePath(path));
|
||||
if (!isSelected) continue;
|
||||
|
||||
if(tn.getChildCount() > 0) continue;
|
||||
Object[] path = checkedPath.getPath();
|
||||
DefaultMutableTreeNode[] modifiedArray = Arrays.stream(Arrays.copyOfRange(path, 1, path.length)).toArray(DefaultMutableTreeNode[]::new);
|
||||
|
||||
ArrayList<String> strings = new ArrayList<>();
|
||||
if(tempMap.containsKey(String.valueOf(modifiedArray[0].getUserObject()))) {
|
||||
strings = tempMap.get(String.valueOf(modifiedArray[0].getUserObject()));
|
||||
ArrayList<String> treePathList = new ArrayList<>();
|
||||
for (int i = 2; i < path.length; i++) {
|
||||
treePathList.add(String.valueOf(((DefaultMutableTreeNode) path[i]).getUserObject()));
|
||||
}
|
||||
String treePath = String.join("/", treePathList);
|
||||
String modname = String.valueOf(((DefaultMutableTreeNode) path[1]).getUserObject());
|
||||
|
||||
String modPath = "";
|
||||
for (int i = 1; i < modifiedArray.length; i++) {
|
||||
modPath += String.valueOf(modifiedArray[i].getUserObject()) + "/";
|
||||
}
|
||||
modPath = modPath.isEmpty() ? "" : modPath.substring(0, modPath.length() - 1);
|
||||
strings.add(modPath);
|
||||
|
||||
tempMap.put((String) modifiedArray[0].getUserObject(), strings);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, ArrayList<String>> entry : tempMap.entrySet()) {
|
||||
String modS = entry.getKey();
|
||||
ArrayList<String> modlistS = entry.getValue();
|
||||
|
||||
if(modlistS.isEmpty()) {
|
||||
for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
|
||||
if (abstractMod.getName().equals(modS)) {
|
||||
modList.add(abstractMod);
|
||||
if (fileChecker.getChanged().containsKey(modname)) {
|
||||
for (ModFile modFile : fileChecker.getChanged().get(modname)) {
|
||||
if (String.join("/", modFile.getPath()).equals(treePath)) {
|
||||
synclist.add(modFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
|
||||
if (abstractMod.getName().equals(modS)) {
|
||||
if(!(abstractMod instanceof Mod)) continue;
|
||||
Mod m = ((Mod) abstractMod).clone();
|
||||
|
||||
for (int i = 0; i < m.getFiles().size(); i++) {
|
||||
boolean found = false;
|
||||
for (String pathS : modlistS) {
|
||||
if(m.getFiles().get(i).getModfileString().equals(pathS)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
m.getFiles().remove(i);
|
||||
if (fileChecker.getAdded().containsKey(modname)) {
|
||||
for (ModFile modFile : fileChecker.getAdded().get(modname)) {
|
||||
if (String.join("/", modFile.getPath()).equals(treePath)) {
|
||||
synclist.add(modFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
synclist.setDeleted(fileChecker.getDeleted());
|
||||
|
||||
modList.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modList;
|
||||
return synclist;
|
||||
}
|
||||
|
||||
public void updateModList(Modset modset) {
|
||||
ListModel<String> model = (ListModel) modList.getModel();
|
||||
// TODO: Show All Mods (keyname)
|
||||
// TODO: Show not installed Mods with red font
|
||||
// TODO: Select Mod if in modset.Mods
|
||||
// TODO: Custom Checkbox Render
|
||||
// TODO: Wenn modset.type == Server alle Checkboxen deaktivieren!
|
||||
// Show not installed Mods with red font
|
||||
// Select Mod if in modset.Mods
|
||||
// Custom Checkbox Render
|
||||
// Wenn modset.type == Server alle Checkboxen deaktivieren!
|
||||
}
|
||||
|
||||
public void updateRepoTree() {
|
||||
@ -629,28 +636,26 @@ public class LauncherGUI implements Observer {
|
||||
collapseAllButton.setEnabled(false);
|
||||
|
||||
DefaultTreeModel model = (DefaultTreeModel) repoTree.getModel();
|
||||
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
|
||||
root.setUserObject("Repository");
|
||||
RepositoryTreeNode root = (RepositoryTreeNode) model.getRoot();
|
||||
root.removeAllChildren();
|
||||
|
||||
for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
|
||||
if (abstractMod instanceof Mod) {
|
||||
// Whole Folder
|
||||
// TODO: Recursives Ordner Parsen und einzelne Treenodes erstellen
|
||||
Mod m = (Mod) abstractMod;
|
||||
DefaultMutableTreeNode modFolder = new DefaultMutableTreeNode(m.getName(), true);
|
||||
RepositoryTreeNode modFolder = new RepositoryTreeNode(m.getName(), true);
|
||||
model.insertNodeInto(modFolder, root, root.getChildCount());
|
||||
|
||||
for (ModFile modfile : m.getFiles()) {
|
||||
|
||||
DefaultMutableTreeNode lastNode = modFolder;
|
||||
RepositoryTreeNode lastNode = modFolder;
|
||||
ArrayList<String> path = modfile.getPath();
|
||||
|
||||
for (int i = 0; i < path.size(); i++) {
|
||||
for (int i = 0; i < path.size() -1; i++) {
|
||||
boolean found = false;
|
||||
|
||||
for (int j = 0; j < lastNode.getChildCount(); j++) {
|
||||
DefaultMutableTreeNode temp = (DefaultMutableTreeNode) lastNode.getChildAt(j);
|
||||
RepositoryTreeNode temp = (RepositoryTreeNode) lastNode.getChildAt(j);
|
||||
if (temp.getUserObject().equals(path.get(i))) {
|
||||
found = true;
|
||||
lastNode = temp;
|
||||
@ -659,26 +664,25 @@ public class LauncherGUI implements Observer {
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
DefaultMutableTreeNode temp = new DefaultMutableTreeNode(path.get(i));
|
||||
RepositoryTreeNode temp = new RepositoryTreeNode(path.get(i));
|
||||
model.insertNodeInto(temp, lastNode, lastNode.getChildCount());
|
||||
lastNode = temp;
|
||||
}
|
||||
}
|
||||
|
||||
model.insertNodeInto(new DefaultMutableTreeNode(modfile.getName()), lastNode, lastNode.getChildCount());
|
||||
model.insertNodeInto(new RepositoryTreeNode(modfile.getName(), getNodeColor(m.getName(), modfile)), lastNode, lastNode.getChildCount());
|
||||
}
|
||||
sort(modFolder);
|
||||
} else if (abstractMod instanceof ModFile) {
|
||||
// Just a Single FIle
|
||||
ModFile m = (ModFile) abstractMod;
|
||||
model.insertNodeInto(new DefaultMutableTreeNode(m.getName(), false), root, root.getChildCount());
|
||||
model.insertNodeInto(new RepositoryTreeNode(m.getName(), getNodeColor(m.getName(), m), false), root, root.getChildCount());
|
||||
}
|
||||
}
|
||||
|
||||
sort(root);
|
||||
setParentColor(root);
|
||||
|
||||
repoTree.clearCheckChangeEventListeners();
|
||||
|
||||
repoTree.resetCheckingState();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -690,38 +694,90 @@ public class LauncherGUI implements Observer {
|
||||
updateTreePanel.repaint();
|
||||
});
|
||||
|
||||
repoTree.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() {
|
||||
@Override
|
||||
public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) {
|
||||
lastSynclist = getSyncList();
|
||||
if (lastSynclist.getSize() != 0)
|
||||
syncSizeLabel.setText(Humanize.binaryPrefix(lastSynclist.getSize()));
|
||||
else syncSizeLabel.setText("0.0 B");
|
||||
if (lastSynclist.getCount() != 0) {
|
||||
syncDownloadButton.setEnabled(true);
|
||||
syncFileCountLabel.setText("" + lastSynclist.getCount());
|
||||
} else {
|
||||
syncDownloadButton.setEnabled(false);
|
||||
syncFileCountLabel.setText("");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expandAllButton.setEnabled(true);
|
||||
collapseAllButton.setEnabled(true);
|
||||
}
|
||||
|
||||
public DefaultMutableTreeNode sort(DefaultMutableTreeNode node) {
|
||||
public Color getNodeColor(String mod, ModFile mf) {
|
||||
if (fileChecker.getAdded().containsKey(mod)) {
|
||||
ArrayList<ModFile> mfList = fileChecker.getAdded().get(mod);
|
||||
for (ModFile modFile : mfList) {
|
||||
if (modFile.getLocaleFile().getPath().equals(mf.getLocaleFile().getPath())) return Color.RED;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileChecker.getChanged().containsKey(mod)) {
|
||||
ArrayList<ModFile> mfList = fileChecker.getChanged().get(mod);
|
||||
for (ModFile modFile : mfList) {
|
||||
if (modFile.getLocaleFile().getPath().equals(mf.getLocaleFile().getPath())) return Color.ORANGE;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setParentColor(RepositoryTreeNode node) {
|
||||
for (TreeNode leaf : node.getAllLeafNodes()) {
|
||||
if (!(leaf instanceof RepositoryTreeNode)) continue;
|
||||
RepositoryTreeNode mLeaf = (RepositoryTreeNode) leaf;
|
||||
TreeNode[] path = mLeaf.getPath();
|
||||
|
||||
if (mLeaf.getLabelColor() == null) continue;
|
||||
for (int i = 0; i < path.length - 1; i++) {
|
||||
if (!(path[i] instanceof RepositoryTreeNode)) continue;
|
||||
RepositoryTreeNode parent = (RepositoryTreeNode) path[i];
|
||||
if (parent.getLabelColor() == mLeaf.getLabelColor()) continue;
|
||||
if (parent.getLabelColor() == Color.RED) continue;
|
||||
parent.setLabelColor(mLeaf.getLabelColor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RepositoryTreeNode sort(RepositoryTreeNode node) {
|
||||
|
||||
//sort alphabetically
|
||||
for(int i = 0; i < node.getChildCount() - 1; i++) {
|
||||
DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
|
||||
for (int i = 0; i < node.getChildCount() - 1; i++) {
|
||||
RepositoryTreeNode child = (RepositoryTreeNode) node.getChildAt(i);
|
||||
String nt = child.getUserObject().toString();
|
||||
|
||||
for(int j = i + 1; j <= node.getChildCount() - 1; j++) {
|
||||
DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j);
|
||||
for (int j = i + 1; j <= node.getChildCount() - 1; j++) {
|
||||
RepositoryTreeNode prevNode = (RepositoryTreeNode) node.getChildAt(j);
|
||||
String np = prevNode.getUserObject().toString();
|
||||
|
||||
if(nt.compareToIgnoreCase(np) > 0) {
|
||||
if (nt.compareToIgnoreCase(np) > 0) {
|
||||
node.insert(child, j);
|
||||
node.insert(prevNode, i);
|
||||
}
|
||||
}
|
||||
if(child.getChildCount() > 0) {
|
||||
if (child.getChildCount() > 0) {
|
||||
sort(child);
|
||||
}
|
||||
}
|
||||
|
||||
//put folders first - normal on Windows and some flavors of Linux but not on Mac OS X.
|
||||
for(int i = 0; i < node.getChildCount() - 1; i++) {
|
||||
DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
|
||||
for(int j = i + 1; j <= node.getChildCount() - 1; j++) {
|
||||
DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j);
|
||||
for (int i = 0; i < node.getChildCount() - 1; i++) {
|
||||
RepositoryTreeNode child = (RepositoryTreeNode) node.getChildAt(i);
|
||||
for (int j = i + 1; j <= node.getChildCount() - 1; j++) {
|
||||
RepositoryTreeNode prevNode = (RepositoryTreeNode) node.getChildAt(j);
|
||||
|
||||
if(!prevNode.isLeaf() && child.isLeaf()) {
|
||||
if (!prevNode.isLeaf() && child.isLeaf()) {
|
||||
node.insert(child, j);
|
||||
node.insert(prevNode, i);
|
||||
}
|
||||
@ -778,30 +834,55 @@ public class LauncherGUI implements Observer {
|
||||
refreshRepoButton.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
} else if(s.equals("fileChecker")) {
|
||||
} else if (s.equals("fileChecker")) {
|
||||
syncCheckButton.setEnabled(true);
|
||||
syncCheckAbortButton.setEnabled(false);
|
||||
syncCheckStatusLabel.setText("Finished!");
|
||||
updateRepoTree();
|
||||
// TODO: Label einfärben
|
||||
// TODO: Enable Tree Checkboxes
|
||||
|
||||
repoTree.setCheckboxesEnabled(true);
|
||||
syncDownloadButton.setEnabled(true);
|
||||
syncAddedFilesLabel.setText(String.valueOf(fileChecker.getAddedCount()));
|
||||
syncChangedFilesLabel.setText(String.valueOf(fileChecker.getChangedCount()));
|
||||
syncDeletedFilesLabel.setText(String.valueOf(fileChecker.getDeletedCount()));
|
||||
|
||||
syncSizeLabel.setText(String.valueOf(fileChecker.getSize())); // TODO: Make Humanreadable
|
||||
syncDownloadAbortButton.setEnabled(false);
|
||||
syncDownloadButton.setEnabled(true);
|
||||
syncPauseButton.setEnabled(false);
|
||||
|
||||
syncChangedFileSizeLabel.setText(Humanize.binaryPrefix(fileChecker.getSize()));
|
||||
} else if (s.equals("fileCheckerStopped")) {
|
||||
syncCheckButton.setEnabled(true);
|
||||
syncCheckAbortButton.setEnabled(false);
|
||||
syncCheckProgress.setValue(0);
|
||||
syncCheckStatusLabel.setText("Failed!");
|
||||
repoTree.setCheckboxesEnabled(false);
|
||||
|
||||
syncDownloadAbortButton.setEnabled(false);
|
||||
syncDownloadButton.setEnabled(false);
|
||||
syncPauseButton.setEnabled(false);
|
||||
|
||||
repoTree.setCheckboxesChecked(false);
|
||||
|
||||
syncAddedFilesLabel.setText("" + 0);
|
||||
syncChangedFilesLabel.setText("" + 0);
|
||||
syncDeletedFilesLabel.setText("" + 0);
|
||||
|
||||
syncSizeLabel.setText("0.0 B");
|
||||
syncChangedFileSizeLabel.setText("0.0 B");
|
||||
} else if (s.equals("syncStopped")) {
|
||||
new Thread(() -> fileChecker.check()).start();
|
||||
} else if (s.equals("syncComplete")) {
|
||||
new Thread(() -> fileChecker.check()).start();
|
||||
} else if (s.equals("syncContinue")) {
|
||||
syncDownloadAbortButton.setEnabled(true);
|
||||
syncPauseButton.setEnabled(true);
|
||||
syncPauseButton.setText(LangUtils.getInstance().getString("pause"));
|
||||
syncDownloadButton.setEnabled(false);
|
||||
} else if (s.equals("syncPaused")) {
|
||||
syncDownloadAbortButton.setEnabled(true);
|
||||
syncPauseButton.setEnabled(true);
|
||||
syncPauseButton.setText(LangUtils.getInstance().getString("resume"));
|
||||
syncDownloadButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import java.util.EventListener;
|
||||
import java.util.EventObject;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by SomethingSomething https://stackoverflow.com/a/21851201/5605489
|
||||
@ -31,6 +32,7 @@ public class JCheckBoxTree extends JTree {
|
||||
// Defining data structure that will enable to fast check-indicate the state of each node
|
||||
// It totally replaces the "selection" mechanism of the JTree
|
||||
private class CheckedNode {
|
||||
boolean isEnabled;
|
||||
boolean isSelected;
|
||||
boolean hasChildren;
|
||||
boolean allChildrenSelected;
|
||||
@ -38,6 +40,7 @@ public class JCheckBoxTree extends JTree {
|
||||
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
|
||||
isSelected = isSelected_;
|
||||
hasChildren = hasChildren_;
|
||||
isEnabled = true;
|
||||
allChildrenSelected = allChildrenSelected_;
|
||||
}
|
||||
}
|
||||
@ -107,6 +110,11 @@ public class JCheckBoxTree extends JTree {
|
||||
return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
|
||||
}
|
||||
|
||||
public boolean isSelected(TreePath path) {
|
||||
CheckedNode cn = nodesCheckingState.get(path);
|
||||
return cn.isSelected;
|
||||
}
|
||||
|
||||
public void resetCheckingState() {
|
||||
nodesCheckingState = new HashMap<TreePath, CheckedNode>();
|
||||
checkedPaths = new HashSet<TreePath>();
|
||||
@ -148,12 +156,21 @@ public class JCheckBoxTree extends JTree {
|
||||
boolean hasFocus) {
|
||||
checkBox.setText(value.toString());
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
|
||||
|
||||
if (node instanceof RepositoryTreeNode && ((RepositoryTreeNode) node).getLabelColor() != null) {
|
||||
checkBox.setForeground(((RepositoryTreeNode) node).getLabelColor());
|
||||
} else {
|
||||
checkBox.setForeground(UIManager.getColor("CheckBox.foreground"));
|
||||
}
|
||||
|
||||
TreePath tp = new TreePath(node.getPath());
|
||||
CheckedNode cn = nodesCheckingState.get(tp);
|
||||
if (cn == null) {
|
||||
return this;
|
||||
}
|
||||
|
||||
checkBox.setSelected(cn.isSelected);
|
||||
checkBox.setEnabled(cn.isEnabled);
|
||||
checkBox.setOpaque(cn.isSelected && cn.hasChildren && !cn.allChildrenSelected);
|
||||
return this;
|
||||
}
|
||||
@ -264,6 +281,26 @@ public class JCheckBoxTree extends JTree {
|
||||
}
|
||||
}
|
||||
|
||||
public void setCheckboxesChecked(boolean state) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
|
||||
checkSubTree(new TreePath(node.getPath()), state);
|
||||
}
|
||||
|
||||
public void setCheckboxesEnabled(TreePath tp, boolean state) {
|
||||
CheckedNode cn = nodesCheckingState.get(tp);
|
||||
cn.isEnabled = state;
|
||||
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
setCheckboxesEnabled(tp.pathByAddingChild(node.getChildAt(i)), state);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCheckboxesEnabled(boolean state) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
|
||||
setCheckboxesEnabled(new TreePath(node.getPath()), state);
|
||||
}
|
||||
|
||||
public void expandAllNodes() {
|
||||
setTreeExpandedState(true);
|
||||
}
|
||||
@ -274,13 +311,13 @@ public class JCheckBoxTree extends JTree {
|
||||
|
||||
private void setTreeExpandedState(boolean expanded) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
|
||||
setNodeExpandedState( node, expanded);
|
||||
setNodeExpandedState(node, expanded);
|
||||
}
|
||||
|
||||
private void setNodeExpandedState(DefaultMutableTreeNode node, boolean expanded) {
|
||||
ArrayList<TreeNode> list = Collections.list(node.children());
|
||||
for (TreeNode treeNode : list) {
|
||||
setNodeExpandedState((DefaultMutableTreeNode)treeNode, expanded);
|
||||
setNodeExpandedState((DefaultMutableTreeNode) treeNode, expanded);
|
||||
}
|
||||
if (!expanded && node.isRoot()) {
|
||||
return;
|
||||
|
@ -0,0 +1,54 @@
|
||||
package de.mc8051.arma3launcher.model;
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.TreeNode;
|
||||
import java.awt.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by gurkengewuerz.de on 26.03.2020.
|
||||
*/
|
||||
public class RepositoryTreeNode extends DefaultMutableTreeNode {
|
||||
|
||||
private Color labelColor = null;
|
||||
|
||||
public RepositoryTreeNode(String userObject, boolean allowChildren) {
|
||||
super(userObject, allowChildren);
|
||||
}
|
||||
|
||||
public RepositoryTreeNode(String userObject, Color labelColor, boolean allowChildren) {
|
||||
super(userObject, allowChildren);
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
public RepositoryTreeNode(String userObject, Color labelColor) {
|
||||
super(userObject);
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
public RepositoryTreeNode(String userObject) {
|
||||
super(userObject);
|
||||
}
|
||||
|
||||
public Color getLabelColor() {
|
||||
return labelColor;
|
||||
}
|
||||
|
||||
public void setLabelColor(Color labelColor) {
|
||||
this.labelColor = labelColor;
|
||||
}
|
||||
|
||||
public Set<TreeNode> getAllLeafNodes() {
|
||||
Set<TreeNode> leafNodes = new HashSet<>();
|
||||
if (this.children == null) {
|
||||
leafNodes.add(this);
|
||||
} else {
|
||||
for (Object child : this.children) {
|
||||
if (child instanceof RepositoryTreeNode)
|
||||
leafNodes.addAll(((RepositoryTreeNode) child).getAllLeafNodes());
|
||||
}
|
||||
}
|
||||
return leafNodes;
|
||||
}
|
||||
}
|
@ -1,10 +1,19 @@
|
||||
package de.mc8051.arma3launcher.objects;
|
||||
|
||||
import de.mc8051.arma3launcher.ArmA3Launcher;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Created by gurkengewuerz.de on 25.03.2020.
|
||||
@ -17,8 +26,11 @@ public class ModFile implements AbstractMod {
|
||||
private String filename;
|
||||
private String extension;
|
||||
private String modfileString;
|
||||
private String sha1sum;
|
||||
private String parent;
|
||||
private String localGeneratedSHA1sum = "";
|
||||
|
||||
public ModFile(File f, String modfile, long size) {
|
||||
public ModFile(File f, String modfile, String parent, long size, String sha1sum) {
|
||||
// File: Abosolut Path
|
||||
// modfile: addons/config/something.pbo
|
||||
// size: size as in metafile on server
|
||||
@ -28,6 +40,12 @@ public class ModFile implements AbstractMod {
|
||||
this.filename = FilenameUtils.getBaseName(modfile);
|
||||
this.extension = FilenameUtils.getExtension(modfile);
|
||||
this.modfileString = modfile;
|
||||
this.sha1sum = sha1sum.toLowerCase();
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public ModFile(File f, String modfile, long size, String sha1sum) {
|
||||
this(f, modfile, null, size, sha1sum);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
@ -48,23 +66,23 @@ public class ModFile implements AbstractMod {
|
||||
|
||||
public ArrayList<String> getPath() {
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
File relativePath = new File("./"+ modfileString);
|
||||
File relativePath = new File("./" + modfileString);
|
||||
|
||||
do {
|
||||
list.add(relativePath.getName());
|
||||
relativePath = relativePath.getParentFile();
|
||||
} while (relativePath.getParentFile() != null);
|
||||
list.remove(0);
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public long getLocalSize() {
|
||||
if(!f.exists() || !f.isFile()) return -1;
|
||||
if (!f.exists() || !f.isFile()) return -1;
|
||||
return f.length();
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
if(!f.exists() || !f.isFile()) return false;
|
||||
if (!f.exists() || !f.isFile()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -79,4 +97,45 @@ public class ModFile implements AbstractMod {
|
||||
public File getLocaleFile() {
|
||||
return f;
|
||||
}
|
||||
|
||||
public String getSHA1Sum() {
|
||||
return sha1sum;
|
||||
}
|
||||
|
||||
public String getLocalGeneratedSHA1Sum() {
|
||||
try {
|
||||
if (localGeneratedSHA1sum.isEmpty() && exists()) {
|
||||
localGeneratedSHA1sum = DigestUtils.sha1Hex(new FileInputStream(f.getAbsolutePath())).toLowerCase();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
return localGeneratedSHA1sum;
|
||||
}
|
||||
|
||||
public String getRemoteFile() {
|
||||
String s = ArmA3Launcher.config.getString("sync.url");
|
||||
if (parent == null || parent.isEmpty()) {
|
||||
return s + "/" + encodeToURL(getName());
|
||||
}
|
||||
|
||||
s += "/" + encodeToURL(parent);
|
||||
for (String seg : getPath()) {
|
||||
s += "/" + encodeToURL(seg);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public String getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
private String encodeToURL(String s) {
|
||||
try {
|
||||
return URLEncoder.encode(s, StandardCharsets.UTF_8.name()).replace("+", "%20").replace("@", "%40");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
package de.mc8051.arma3launcher.repo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Created by gurkengewuerz.de on 24.03.2020.
|
||||
*/
|
||||
public class DownloadThread implements Runnable {
|
||||
|
||||
private ProcessBuilder processBuilder;
|
||||
private Process process;
|
||||
private Thread thread;
|
||||
|
||||
private Status status = Status.PENDING;
|
||||
|
||||
public DownloadThread(ProcessBuilder processBuilder) {
|
||||
this.processBuilder = processBuilder;
|
||||
this.processBuilder.redirectErrorStream(true);
|
||||
|
||||
thread = new Thread(this);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
process.destroy();
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
process = processBuilder.start();
|
||||
status = Status.RUNNING;
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
while ( (line = reader.readLine()) != null && !thread.isInterrupted()) {
|
||||
System.out.println(line);
|
||||
}
|
||||
|
||||
int exitVal = process.waitFor();
|
||||
if(exitVal == 0) status = Status.FINNISHED;
|
||||
else status = Status.ERROR;
|
||||
|
||||
System.out.println(exitVal);
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
|
||||
status = Status.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private enum Status {
|
||||
PENDING(0),
|
||||
RUNNING(1),
|
||||
FINNISHED(2),
|
||||
ERROR(3);
|
||||
|
||||
Status(int i) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -30,10 +30,10 @@ public class FileChecker implements Observable {
|
||||
private JProgressBar pb;
|
||||
private boolean stop = false;
|
||||
|
||||
ArrayList<Path> deleted = new ArrayList<>();
|
||||
HashMap<String, ArrayList<ModFile>> changed = new HashMap<>();
|
||||
private ArrayList<Path> deleted = new ArrayList<>();
|
||||
private HashMap<String, ArrayList<ModFile>> changed = new HashMap<>();
|
||||
int changedCount = 0;
|
||||
HashMap<String, ArrayList<ModFile>> added = new HashMap<>();
|
||||
private HashMap<String, ArrayList<ModFile>> added = new HashMap<>();
|
||||
int addedCount = 0;
|
||||
|
||||
long size = 0;
|
||||
@ -66,7 +66,8 @@ public class FileChecker implements Observable {
|
||||
Mod m = (Mod) abstractMod;
|
||||
|
||||
for (ModFile mf : m.getFiles()) {
|
||||
checkFile(mf.getName(), mf);
|
||||
checkFile(m.getName(), mf);
|
||||
|
||||
i++;
|
||||
int finalI = i;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -99,16 +100,21 @@ public class FileChecker implements Observable {
|
||||
}
|
||||
|
||||
private void checkFile(String mod, ModFile mf) {
|
||||
// TODO: Add mf to Array if Array already exists
|
||||
ArrayList<ModFile> temp = new ArrayList<>();
|
||||
|
||||
if(!mf.exists()) {
|
||||
added.put(mod, mf);
|
||||
if(added.containsKey(mod)) temp =added.get(mod);
|
||||
temp.add(mf);
|
||||
added.put(mod, temp);
|
||||
addedCount++;
|
||||
size += mf.getSize();
|
||||
return;
|
||||
}
|
||||
|
||||
if(mf.getLocalSize() != mf.getSize()) {
|
||||
changed.put(mod, mf);
|
||||
if(mf.getLocalSize() != mf.getSize() || !mf.getSHA1Sum().equalsIgnoreCase(mf.getLocalGeneratedSHA1Sum())) {
|
||||
if(changed.containsKey(mod)) temp =changed.get(mod);
|
||||
temp.add(mf);
|
||||
changed.put(mod, temp);
|
||||
changedCount++;
|
||||
size += mf.getSize();
|
||||
return;
|
||||
|
@ -151,16 +151,18 @@ public class RepositoryManger implements Observable {
|
||||
Iterator<String> keys = content.keys();
|
||||
while (keys.hasNext()) {
|
||||
String modfile = keys.next();
|
||||
long modfilesize = content.getLong(modfile);
|
||||
JSONObject jo = content.getJSONObject(modfile);
|
||||
long modfilesize = jo.getLong("size");
|
||||
String sha1 = jo.getString("sha1");
|
||||
|
||||
modFiles.add(new ModFile(new File(finalModPath + File.separator + modname + File.separator + modfile), modfile, modfilesize));
|
||||
modFiles.add(new ModFile(new File(finalModPath + File.separator + modname + File.separator + modfile), modfile, modname, modfilesize, sha1));
|
||||
RepositoryManger.MOD_LIST_SIZE++;
|
||||
}
|
||||
|
||||
MOD_LIST.add(new Mod(modname, modsize, modFiles));
|
||||
} else {
|
||||
// Single File
|
||||
MOD_LIST.add(new ModFile(new File(finalModPath + File.separator + modname), modname, modsize));
|
||||
MOD_LIST.add(new ModFile(new File(finalModPath + File.separator + modname), modname, modsize, jsonMod.getString("sha1")));
|
||||
RepositoryManger.MOD_LIST_SIZE++;
|
||||
}
|
||||
|
||||
|
108
src/main/java/de/mc8051/arma3launcher/repo/SyncList.java
Normal file
108
src/main/java/de/mc8051/arma3launcher/repo/SyncList.java
Normal file
@ -0,0 +1,108 @@
|
||||
package de.mc8051.arma3launcher.repo;
|
||||
|
||||
import de.mc8051.arma3launcher.objects.AbstractMod;
|
||||
import de.mc8051.arma3launcher.objects.Mod;
|
||||
import de.mc8051.arma3launcher.objects.ModFile;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Created by gurkengewuerz.de on 26.03.2020.
|
||||
*/
|
||||
public class SyncList extends ArrayList<AbstractMod> {
|
||||
|
||||
private long size = 0;
|
||||
private int count = 0;
|
||||
|
||||
private ArrayList<Path> deleted = new ArrayList<>();
|
||||
|
||||
private SyncList(long size, int count) {
|
||||
this.size = size;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public SyncList() {
|
||||
|
||||
}
|
||||
|
||||
public void setDeleted(ArrayList<Path> deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(AbstractMod abstractMod) {
|
||||
addedMod(abstractMod);
|
||||
return super.add(abstractMod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends AbstractMod> c) {
|
||||
for (AbstractMod abstractMod : c) {
|
||||
addedMod(abstractMod);
|
||||
}
|
||||
return super.addAll(c);
|
||||
}
|
||||
|
||||
private void addedMod(AbstractMod abstractMod) {
|
||||
if (abstractMod instanceof Mod) {
|
||||
Mod mod = (Mod) abstractMod;
|
||||
for (ModFile mf : mod.getFiles()) {
|
||||
size += mf.getSize();
|
||||
count++;
|
||||
}
|
||||
} else if (abstractMod instanceof ModFile) {
|
||||
ModFile mf = (ModFile) abstractMod;
|
||||
size += mf.getSize();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractMod remove(int index) {
|
||||
AbstractMod abstractMod = get(index);
|
||||
|
||||
if (abstractMod instanceof Mod) {
|
||||
Mod mod = (Mod) abstractMod;
|
||||
for (ModFile mf : mod.getFiles()) {
|
||||
size -= mf.getSize();
|
||||
}
|
||||
} else if (abstractMod instanceof ModFile) {
|
||||
ModFile mf = (ModFile) abstractMod;
|
||||
size -= mf.getSize();
|
||||
}
|
||||
|
||||
return super.remove(index);
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public SyncList clone() {
|
||||
SyncList clone = new SyncList();
|
||||
clone.addAll(this);
|
||||
clone.getDeleted().addAll(deleted);
|
||||
clone.setSize(size);
|
||||
clone.setDeleted(deleted);
|
||||
return clone;
|
||||
}
|
||||
|
||||
public ArrayList<Path> getDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
@ -1,8 +1,304 @@
|
||||
package de.mc8051.arma3launcher.repo;
|
||||
|
||||
import co.bitshfted.xapps.zsync.Zsync;
|
||||
import co.bitshfted.xapps.zsync.ZsyncException;
|
||||
import co.bitshfted.xapps.zsync.ZsyncObserver;
|
||||
import de.mc8051.arma3launcher.ArmA3Launcher;
|
||||
import de.mc8051.arma3launcher.LauncherGUI;
|
||||
import de.mc8051.arma3launcher.interfaces.Observable;
|
||||
import de.mc8051.arma3launcher.interfaces.Observer;
|
||||
import de.mc8051.arma3launcher.objects.AbstractMod;
|
||||
import de.mc8051.arma3launcher.objects.ModFile;
|
||||
import humanize.Humanize;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Created by gurkengewuerz.de on 25.03.2020.
|
||||
*/
|
||||
public class Syncer {
|
||||
// FilenameUtils.directoryContains
|
||||
public class Syncer extends ZsyncObserver implements Observable {
|
||||
|
||||
private List<Observer> observerList = new ArrayList<>();
|
||||
|
||||
private boolean stopped = false;
|
||||
private boolean paused = false;
|
||||
private boolean running = false;
|
||||
|
||||
private ModFile currentDownload = null;
|
||||
private SyncList modlist;
|
||||
|
||||
private boolean currentDownload_failed = false;
|
||||
private boolean controlfile_downloaded = false;
|
||||
private int failed = 0;
|
||||
private int success = 0;
|
||||
|
||||
long syncSize;
|
||||
int syncCount;
|
||||
|
||||
long downloadStarted;
|
||||
long downloadEnded;
|
||||
long downloadSize;
|
||||
long downloadDownloaded;
|
||||
|
||||
private Zsync zsync;
|
||||
private LauncherGUI gui;
|
||||
|
||||
public Syncer(LauncherGUI gui) {
|
||||
zsync = new Zsync();
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
public void sync(SyncList ml) {
|
||||
modlist = ml;
|
||||
|
||||
stopped = false;
|
||||
paused = false;
|
||||
running = true;
|
||||
|
||||
currentDownload = null;
|
||||
|
||||
failed = 0;
|
||||
success = 0;
|
||||
|
||||
syncSize = ml.getSize();
|
||||
syncCount = ml.getCount();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
gui.syncDownloadProgress.setMaximum(syncCount);
|
||||
gui.syncDownloadProgress.setValue(0);
|
||||
});
|
||||
|
||||
boolean lastPause = false;
|
||||
while (running) {
|
||||
if (stopped) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
if (modlist.isEmpty()) {
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (paused) {
|
||||
if (!lastPause) {
|
||||
lastPause = true;
|
||||
notifyObservers("syncPaused");
|
||||
}
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
continue;
|
||||
} else if (lastPause) {
|
||||
lastPause = false;
|
||||
notifyObservers("syncContinue");
|
||||
}
|
||||
|
||||
if (currentDownload != null) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractMod abstractMod = modlist.get(0);
|
||||
|
||||
ModFile mf = null;
|
||||
if (abstractMod instanceof ModFile) {
|
||||
mf = (ModFile) abstractMod;
|
||||
}
|
||||
|
||||
if (mf != null) {
|
||||
Zsync.Options o = new Zsync.Options();
|
||||
o.setOutputFile(Paths.get(mf.getLocaleFile().getAbsolutePath()));
|
||||
|
||||
try {
|
||||
currentDownload = mf;
|
||||
currentDownload_failed = false;
|
||||
controlfile_downloaded = false;
|
||||
|
||||
zsync.zsync(URI.create(mf.getRemoteFile() + ".zsync"), o, this);
|
||||
} catch (ZsyncException | IllegalArgumentException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
} else {
|
||||
modlist.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
deleteFiles();
|
||||
cleanUpEmptyFolders();
|
||||
|
||||
if (stopped) {
|
||||
notifyObservers("syncStopped");
|
||||
} else {
|
||||
notifyObservers("syncComplete");
|
||||
}
|
||||
}
|
||||
|
||||
public void finnishCurrent() {
|
||||
modlist.remove(0);
|
||||
currentDownload = null;
|
||||
}
|
||||
|
||||
public void deleteFiles() {
|
||||
modlist.getDeleted().stream()
|
||||
.filter((p) -> p.toFile().exists())
|
||||
.filter((p) -> p.toFile().canRead())
|
||||
.filter((p) -> p.toFile().canWrite())
|
||||
.forEach((p) -> p.toFile().delete());
|
||||
}
|
||||
|
||||
public void cleanUpEmptyFolders() {
|
||||
try {
|
||||
String modPath = ArmA3Launcher.user_config.get("client", "modPath");
|
||||
if (modPath == null) modPath = "";
|
||||
if (modPath.isEmpty()) return;
|
||||
Files.find(Paths.get(modPath),
|
||||
Integer.MAX_VALUE,
|
||||
(filePath, fileAttr) -> fileAttr.isDirectory())
|
||||
.filter((p) -> p.toFile().canRead())
|
||||
.filter((p) -> p.toFile().canWrite())
|
||||
.filter((p) -> p.toFile().list().length == 0)
|
||||
.forEach((p) -> p.toFile().delete());
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zsyncFailed(Exception exception) {
|
||||
super.zsyncFailed(exception);
|
||||
currentDownload_failed = true;
|
||||
System.out.println("Zsync failed " + exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void zsyncComplete() {
|
||||
super.zsyncComplete();
|
||||
|
||||
downloadEnded = System.nanoTime();
|
||||
System.out.println(downloadSize);
|
||||
System.out.println(downloadEnded - downloadStarted);
|
||||
System.out.println((downloadSize / (downloadEnded - downloadStarted)) / 1000);
|
||||
|
||||
System.out.println("Zsync complete");
|
||||
|
||||
if (currentDownload_failed)
|
||||
failed++;
|
||||
else success++;
|
||||
|
||||
final long finalSize = syncSize - modlist.getSize();
|
||||
int i = success + failed;
|
||||
int percentage = (int) ((double)i / (double)Long.valueOf(syncCount).intValue() * 100);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
gui.syncDownloadProgress.setValue(i);
|
||||
gui.syncDownloadedLabel.setText(Humanize.binaryPrefix(finalSize) + " " + " (" + failed + " failed)");
|
||||
gui.syncDownloadProgress.setString(percentage + "%");
|
||||
});
|
||||
|
||||
finnishCurrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void controlFileDownloadingStarted(URI uri, long length) {
|
||||
super.controlFileDownloadingStarted(uri, length);
|
||||
System.out.println("controlFileDownloadingStarted " + length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remoteFileDownloadingStarted(URI uri, long length) {
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bytesDownloaded(long bytes) {
|
||||
super.bytesDownloaded(bytes);
|
||||
// System.out.println("Downloaded " + bytes);
|
||||
downloadDownloaded += bytes;
|
||||
|
||||
// TODO: Fix file Download Progress
|
||||
if (controlfile_downloaded) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
gui.syncFileProgress.setValue(Long.valueOf(downloadDownloaded).intValue());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isStopped() {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
return paused;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
}
|
||||
|
||||
public int getCountFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
public int getCountSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addObserver(Observer observer) {
|
||||
observerList.add(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObserver(Observer observer) {
|
||||
observerList.remove(observer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyObservers(String obj) {
|
||||
for (Observer obs : observerList) obs.update(obj);
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +88,12 @@ collapse_all=Alles einklappen
|
||||
same_mod_arma_dir_msg=Das ArmA sowie Mod Verzeichnis dürfen nicht identisch sein.
|
||||
same_mod_arma_dir=Gleiches Verzeichnis
|
||||
check_local_addons=Lokale Dateien überprüfen
|
||||
changed_files=Veränderte Datien
|
||||
added_files=Hinzugefügte Datien
|
||||
changed_filesize=Veränderte Größe
|
||||
added_files=Hinzugefügte Dateien
|
||||
deleted_files=Gelöschte Dateien
|
||||
changed_filesize=Veränderte Größe
|
||||
changed_filesize_tooltip=Dies ist die ungefähre Dateigröße die Maximal runtergeladen wird
|
||||
resume=Fortsetzen
|
||||
changed_filesize=Veränderte Dateien
|
||||
changed_files=Geänderte Dateien
|
||||
file_count=Dateianzahl
|
@ -86,3 +86,12 @@ repository_content=Repository Content
|
||||
collapse_all=Collapse All
|
||||
same_mod_arma_dir_msg=The ArmA and Mod directory must not be identical.
|
||||
same_mod_arma_dir=Same directory
|
||||
resume=Resume
|
||||
added_files=Added files
|
||||
deleted_files=Deleted files
|
||||
update_repository=Update repository
|
||||
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
|
Loading…
Reference in New Issue
Block a user