diff --git a/linux/generateRepo.sh b/linux/generateRepo.sh
index dae385a..5bb36ea 100644
--- a/linux/generateRepo.sh
+++ b/linux/generateRepo.sh
@@ -28,15 +28,15 @@ while IFS= read -r line; do
mustgenerate=false
zsyncfile="${line}.zsync"
- filebyte=$(wc -c < ${line})
- filedate=$(stat -c %Y ${line})
+ filebyte=$(wc -c < "${line}")
+ filedate=$(stat -c %Y "${line}")
- zsyncfiledate=$(strings ${zsyncfile} 2>/dev/null | grep -m 1 MTime | cut -d" " -f2-)
+ zsyncfiledate=$(strings "${zsyncfile}" 2>/dev/null | grep -m 1 MTime | cut -d" " -f2-)
if [ ! -f "$zsyncfile" ]; then
echo "$zsyncfile does not exist"
mustgenerate=true
- elif [[ ! $(strings ${zsyncfile} | grep -m 1 Length | cut -d" " -f2) == $filebyte ]]; then # Check file length
+ elif [[ ! $(strings "${zsyncfile}" | grep -m 1 Length | cut -d" " -f2) == $filebyte ]]; then # Check file length
echo "$zsyncfile does not have corret length"
mustgenerate=true
elif [[ ! $filedate == $(date -d "${zsyncfiledate}" +"%s") ]]; then # Check date
@@ -46,11 +46,11 @@ while IFS= read -r line; do
if [ "$mustgenerate" = true ]; then
echo "Generate $zsyncfile"
- rm ${zsyncfile} 2> /dev/null
- dirfile=$(dirname ${line})
- filename=$(basename ${line})
- filenamezsync=$(basename ${zsyncfile})
- $(cd ${dirfile} && zsyncmake -o ${filenamezsync} ${filename})
+ rm "${zsyncfile}" 2> /dev/null
+ dirfile=$(dirname "${line}")
+ filename=$(basename "${line}")
+ filenamezsync=$(basename "${zsyncfile}")
+ $(cd "${dirfile}" && zsyncmake -o "${filenamezsync}" "${filename}")
if [ $? -eq 0 ]; then
echo "Success: Generated ${zsyncfile}"
else
@@ -67,10 +67,10 @@ echo -e "===== ===== ===== ===== ===== =====\n"
echo "===== ===== ===== DELETE SINGLE ZFILE WITHOUT FILE ===== ===== ====="
ZSYNCLIST=$(find . -name "*.zsync")
while IFS= read -r zfile; do
- ORIG=$(echo ${zfile} | rev | cut -c7- | rev)
+ ORIG=$(echo "${zfile}" | rev | cut -c7- | rev)
if [ ! -f "$ORIG" ]; then
echo "$ORIG does not exist"
- rm ${zfile}
+ rm "${zfile}"
fi
done <<< "$ZSYNCLIST"
echo -e "===== ===== ===== ===== ===== =====\n"
@@ -85,18 +85,18 @@ while IFS= read -r folder; do
echo "is dir"
x=""
foldersize=0
- FILEFOLDER=$(find ${folder} -type f ! -path "*.zsync" | sed 's|^./||')
+ FILEFOLDER=$(find "${folder}" -type f ! -path "*.zsync" | sed 's|^./||')
while IFS= read -r folderfile; do
- filebyte=$(wc -c < ${folderfile})
+ filebyte=$(wc -c < "${folderfile}")
foldersize=$(expr $foldersize + $filebyte)
- name=$(echo ${folderfile} | cut -d"/" -f2-)
+ name=$(echo "${folderfile}" | cut -d"/" -f2-)
x="\"${name}\":${filebyte},${x}"
done <<< "$FILEFOLDER"
x=$(echo ${x} | rev | cut -c2- | rev)
JSONDATA+=( "\"${folder}\": {\"size\":${foldersize},\"content\":{${x}}}" )
else
echo "is file"
- filebyte=$(wc -c < ${folder})
+ filebyte=$(wc -c < "${folder}")
JSONDATA+=( "\"${folder}\": {\"size\":${filebyte}}" )
fi
done <<< "$FILELIST"
diff --git a/pom.xml b/pom.xml
index e69ada0..3e2b0ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,6 +57,11 @@
zsyncer
-f69d844481-1
+
+ com.squareup.okhttp3
+ okhttp
+ 4.4.1
+
diff --git a/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java b/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java
index fe83004..b0b327f 100644
--- a/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java
+++ b/src/main/java/de/mc8051/arma3launcher/ArmA3Launcher.java
@@ -3,7 +3,6 @@ package de.mc8051.arma3launcher;
import com.formdev.flatlaf.FlatDarkLaf;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
-import de.mc8051.arma3launcher.repo.RepositoryManger;
import de.mc8051.arma3launcher.steam.SteamTimer;
import org.ini4j.Ini;
@@ -12,7 +11,6 @@ import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
-import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Timer;
@@ -81,7 +79,7 @@ public class ArmA3Launcher {
frame.setLocationRelativeTo(null);
steamTimer.scheduleAtFixedRate(
- new SteamTimer(gui),
+ new SteamTimer(),
500, // run first occurrence immediately
10000); // run every thirty seconds
diff --git a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form
index aacc4d2..07ce0d0 100644
--- a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form
+++ b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.form
@@ -3,7 +3,7 @@
-
+
@@ -237,6 +237,7 @@
+
@@ -294,34 +295,73 @@
-
-
-
-
-
+
-
+
-
+
-
+
+
-
+
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -367,7 +407,7 @@
-
+
@@ -378,7 +418,7 @@
-
+
@@ -386,27 +426,23 @@
-
-
-
-
-
-
+
-
+
+
-
+
-
+
@@ -417,19 +453,21 @@
-
+
+
-
+
+
@@ -440,6 +478,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -516,7 +602,7 @@
-
+
@@ -613,27 +699,30 @@
-
+
+
-
+
+
-
+
+
@@ -690,6 +779,7 @@
+
@@ -1079,6 +1169,7 @@
+
diff --git a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java
index 8c5f8d7..ce730ee 100644
--- a/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java
+++ b/src/main/java/de/mc8051/arma3launcher/LauncherGUI.java
@@ -1,13 +1,19 @@
package de.mc8051.arma3launcher;
import de.mc8051.arma3launcher.interfaces.Observer;
+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.ServerTableModel;
+import de.mc8051.arma3launcher.objects.AbstractMod;
+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.FileChecker;
import de.mc8051.arma3launcher.repo.RepositoryManger;
+import de.mc8051.arma3launcher.steam.SteamTimer;
import de.mc8051.arma3launcher.utils.Callback;
import de.mc8051.arma3launcher.utils.LangUtils;
@@ -16,6 +22,9 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
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.TreePath;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -28,6 +37,11 @@ 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;
@@ -97,25 +111,50 @@ public class LauncherGUI implements Observer {
private JScrollPane settingScrollPane;
private JCheckBox settingsUseWorkshopBox;
private JTree tree1;
- private JButton allesAuswählenButton;
- private JButton allesAusklappenButton;
- private JProgressBar progressBar1;
- private JButton abbrechenButton;
- private JButton überprüfenButton;
+ private JButton expandAllButton;
+ private JProgressBar syncCheckProgress;
+ private JButton syncCheckAbortButton;
+ private JButton syncCheckButton;
private JProgressBar progressBar2;
private JProgressBar progressBar3;
- private JButton downloadButton;
- private JButton abbrechenButton1;
- private JButton pauseButton;
+ private JButton syncDownloadButton;
+ private JButton syncDownloadAbortButton;
+ private JButton syncPauseButton;
+ private JComboBox comboBox1;
+ private JButton refreshRepoButton;
+ private JPanel updateTreePanel;
+ private JScrollPane updateTreeScrolPane;
+ private JButton collapseAllButton;
+ private JLabel syncCheckStatusLabel;
+ private JLabel syncDeletedFilesLabel;
+ private JLabel syncAddedFilesLabel;
+ private JLabel syncChangedFilesLabel;
+ private JLabel syncSizeLabel;
+
+ 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
*/
public LauncherGUI() {
+ fileChecker = new FileChecker(syncCheckProgress);
+
RepositoryManger.getInstance().addObserver(this);
+ SteamTimer.addObserver(this);
+ fileChecker.addObserver(this);
+
+ updateTreePanel.remove(tree1);
+
+ repoTree = new JCheckBoxTree();
+ updateTreePanel.add(repoTree, BorderLayout.CENTER);
+
+ updateTreePanel.revalidate();
+ updateTreePanel.repaint();
tabbedPane1.setUI(new BasicTabbedPaneUI() {
private final Insets borderInsets = new Insets(0, 0, 0, 0);
@@ -184,7 +223,7 @@ public class LauncherGUI implements Observer {
Modset modset = (Modset) m.getElementAt(presetList.getSelectedIndex());
System.out.println(modset.getName());
- if(modset.getType() == Modset.Type.SERVER) {
+ if (modset.getType() == Modset.Type.SERVER) {
renamePresetButton.setEnabled(false);
removePresetButtom.setEnabled(false);
} else {
@@ -226,8 +265,45 @@ public class LauncherGUI implements Observer {
});
settingScrollPane.getVerticalScrollBar().setUnitIncrement(16);
+ updateTreeScrolPane.getVerticalScrollBar().setUnitIncrement(16);
- RepositoryManger.getInstance().refreshMeta();
+ refreshRepoButton.addActionListener(e -> RepositoryManger.getInstance().refreshModset());
+ expandAllButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ repoTree.expandAllNodes();
+ }
+ });
+ collapseAllButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ repoTree.collapseAllNodes();
+ }
+ });
+
+ new Thread(() -> {
+ RepositoryManger.getInstance().refreshMeta();
+ RepositoryManger.getInstance().refreshModset();
+ }).start();
+
+ syncCheckButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ syncCheckButton.setEnabled(false);
+ syncCheckAbortButton.setEnabled(true);
+ syncCheckStatusLabel.setText("Running!");
+ new Thread(() -> fileChecker.check()).start();
+
+ // TODO: disable JTree Checkboxes
+ }
+ });
+
+ syncCheckAbortButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ fileChecker.stop();
+ }
+ });
}
public static void infoBox(String infoMessage, String titleBar) {
@@ -261,9 +337,53 @@ public class LauncherGUI implements Observer {
}
public void techCheck() {
- // Arma Path set
- // Steam running
- // Arma not running
+ boolean pathSet = ArmA3Launcher.user_config.get("client").containsKey("armaPath") && ArmA3Launcher.user_config.get("client").containsKey("modPath");
+ if (SteamTimer.arma_running) {
+ playButton.setEnabled(false);
+ playPresetButton.setEnabled(false);
+ syncCheckButton.setEnabled(false);
+ refreshRepoButton.setEnabled(false);
+
+ playButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
+ playPresetButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
+ syncCheckButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
+ } else {
+ if (SteamTimer.steam_running) {
+ if (pathSet) {
+ playButton.setEnabled(true);
+ playPresetButton.setEnabled(true);
+
+ playButton.setToolTipText(null);
+ playPresetButton.setToolTipText(null);
+ } else {
+ playButton.setEnabled(false);
+ playPresetButton.setEnabled(false);
+
+ playButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
+ playPresetButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
+ }
+ } else {
+ playButton.setEnabled(false);
+ playPresetButton.setEnabled(false);
+
+ playButton.setToolTipText(LangUtils.getInstance().getString("steam_not_running"));
+ playPresetButton.setToolTipText(LangUtils.getInstance().getString("steam_not_running"));
+ }
+
+ if (pathSet) {
+ syncCheckButton.setEnabled(true);
+ refreshRepoButton.setEnabled(true);
+
+ syncCheckButton.setToolTipText(null);
+ refreshRepoButton.setToolTipText(null);
+ } else {
+ syncCheckButton.setEnabled(true);
+ refreshRepoButton.setEnabled(true);
+
+ syncCheckButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
+ refreshRepoButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
+ }
+ }
}
public boolean checkArmaPath(String path) {
@@ -318,7 +438,15 @@ public class LauncherGUI implements Observer {
SwingUtilities.invokeLater(() -> warnBox(LangUtils.getInstance().getString("not_arma_dir_msg"), LangUtils.getInstance().getString("not_arma_dir")));
return false;
}
+
+ String modPath = ArmA3Launcher.user_config.get("client", "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;
+ }
+
settingsArmaPathText.setText(sPath);
+ techCheck();
return true;
}
});
@@ -326,7 +454,16 @@ public class LauncherGUI implements Observer {
initFolderChooser(settingsModsPathText, settingsModsPathBtn, "modPath", Parameter.ParameterType.CLIENT, new Callback.JFileSelectCallback() {
@Override
public boolean allowSelection(File path) {
- settingsModsPathText.setText(path.getAbsolutePath());
+ String sPath = path.getAbsolutePath();
+
+ String armaPath = ArmA3Launcher.user_config.get("client", "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;
+ }
+
+ settingsModsPathText.setText(sPath);
+ RepositoryManger.getInstance().refreshModset();
return true;
}
});
@@ -413,9 +550,73 @@ public class LauncherGUI implements Observer {
spinner.addChangeListener(new SettingsHandler.SpinnerListener(paraObj));
}
+ public ArrayList getSyncList() {
+ ArrayList modList = new ArrayList<>();
+
+ HashMap> tempMap = new HashMap<>();
+ for (TreePath checkedPath : repoTree.getCheckedPaths()) {
+ DefaultMutableTreeNode tn = (DefaultMutableTreeNode)checkedPath.getLastPathComponent();
+
+ if(tn.getChildCount() > 0) continue;
+ Object[] path = checkedPath.getPath();
+ DefaultMutableTreeNode[] modifiedArray = Arrays.stream(Arrays.copyOfRange(path, 1, path.length)).toArray(DefaultMutableTreeNode[]::new);
+
+ ArrayList strings = new ArrayList<>();
+ if(tempMap.containsKey(String.valueOf(modifiedArray[0].getUserObject()))) {
+ strings = tempMap.get(String.valueOf(modifiedArray[0].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> entry : tempMap.entrySet()) {
+ String modS = entry.getKey();
+ ArrayList modlistS = entry.getValue();
+
+ if(modlistS.isEmpty()) {
+ for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
+ if (abstractMod.getName().equals(modS)) {
+ modList.add(abstractMod);
+ 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);
+ }
+ }
+
+ modList.add(m);
+ }
+ }
+ }
+ }
+
+ return modList;
+ }
+
public void updateModList(Modset modset) {
- ListModel model = (ListModel)modList.getModel();
- // TODO: RepositoryManger.downloadModlist
+ ListModel model = (ListModel) modList.getModel();
// TODO: Show All Mods (keyname)
// TODO: Show not installed Mods with red font
// TODO: Select Mod if in modset.Mods
@@ -423,27 +624,184 @@ public class LauncherGUI implements Observer {
// TODO: Wenn modset.type == Server alle Checkboxen deaktivieren!
}
+ public void updateRepoTree() {
+ expandAllButton.setEnabled(false);
+ collapseAllButton.setEnabled(false);
+
+ DefaultTreeModel model = (DefaultTreeModel) repoTree.getModel();
+ DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
+ root.setUserObject("Repository");
+ 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);
+ model.insertNodeInto(modFolder, root, root.getChildCount());
+
+ for (ModFile modfile : m.getFiles()) {
+
+ DefaultMutableTreeNode lastNode = modFolder;
+ ArrayList path = modfile.getPath();
+
+ for (int i = 0; i < path.size(); i++) {
+ boolean found = false;
+
+ for (int j = 0; j < lastNode.getChildCount(); j++) {
+ DefaultMutableTreeNode temp = (DefaultMutableTreeNode) lastNode.getChildAt(j);
+ if (temp.getUserObject().equals(path.get(i))) {
+ found = true;
+ lastNode = temp;
+ break;
+ }
+ }
+
+ if (!found) {
+ DefaultMutableTreeNode temp = new DefaultMutableTreeNode(path.get(i));
+ model.insertNodeInto(temp, lastNode, lastNode.getChildCount());
+ lastNode = temp;
+ }
+ }
+
+ model.insertNodeInto(new DefaultMutableTreeNode(modfile.getName()), 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());
+ }
+ }
+
+ sort(root);
+
+ repoTree.clearCheckChangeEventListeners();
+
+ repoTree.resetCheckingState();
+
+ SwingUtilities.invokeLater(() -> {
+ model.nodeChanged(root);
+ model.reload();
+ repoTree.revalidate();
+ repoTree.repaint();
+ updateTreePanel.revalidate();
+ updateTreePanel.repaint();
+ });
+
+ expandAllButton.setEnabled(true);
+ collapseAllButton.setEnabled(true);
+ }
+
+ public DefaultMutableTreeNode sort(DefaultMutableTreeNode node) {
+
+ //sort alphabetically
+ for(int i = 0; i < node.getChildCount() - 1; i++) {
+ DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
+ String nt = child.getUserObject().toString();
+
+ for(int j = i + 1; j <= node.getChildCount() - 1; j++) {
+ DefaultMutableTreeNode prevNode = (DefaultMutableTreeNode) node.getChildAt(j);
+ String np = prevNode.getUserObject().toString();
+
+ if(nt.compareToIgnoreCase(np) > 0) {
+ node.insert(child, j);
+ node.insert(prevNode, i);
+ }
+ }
+ 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);
+
+ if(!prevNode.isLeaf() && child.isLeaf()) {
+ node.insert(child, j);
+ node.insert(prevNode, i);
+ }
+ }
+ }
+
+ return node;
+
+ }
+
+
@Override
- public void update(Object o) {
- String s = String.valueOf(o);
+ public void update(String s) {
+ System.out.println(s);
+ if (s.equals(RepositoryManger.Type.METADATA.toString())) {
+ switch (RepositoryManger.getInstance().getStatus(RepositoryManger.Type.METADATA)) {
+ case ERROR:
+ errorBox("Metadata download failed. Is the server availaible? Do you have an active internet connection?", "Download failed");
+ System.exit(1);
+ break;
- if (s.equals("refreshMeta")) {
+ case FINNISHED:
+ SwingUtilities.invokeLater(() -> {
+ ServerTableModel model = (ServerTableModel) serverTable.getModel();
+
+ Server.SERVER_LIST.forEach((name, server) -> model.add(server));
+ });
+
+ SwingUtilities.invokeLater(() -> {
+ PresetTableModel model = (PresetTableModel) presetList.getModel();
+ model.clear();
+
+ model.add(new Modset("--Server", Modset.Type.CLIENT, null, false));
+
+ Modset.MODSET_LIST.forEach((name, set) -> {
+ model.add(set);
+ });
+ });
+ break;
+ }
+ } else if (s.equals("steamtimer")) {
SwingUtilities.invokeLater(() -> {
- ServerTableModel model = (ServerTableModel) serverTable.getModel();
-
- Server.SERVER_LIST.forEach((name, server) -> model.add(server));
+ updateLabels(SteamTimer.steam_running, SteamTimer.arma_running);
+ techCheck();
});
+ } else if (s.equals(RepositoryManger.Type.MODSET.toString())) {
+ switch (RepositoryManger.getInstance().getStatus(RepositoryManger.Type.METADATA)) {
+ case FINNISHED:
+ refreshRepoButton.setEnabled(true);
+ updateRepoTree();
+ break;
- SwingUtilities.invokeLater(() -> {
- PresetTableModel model = (PresetTableModel) presetList.getModel();
- model.clear();
+ case RUNNING:
+ refreshRepoButton.setEnabled(false);
+ break;
+ }
+ } else if(s.equals("fileChecker")) {
+ syncCheckButton.setEnabled(true);
+ syncCheckAbortButton.setEnabled(false);
+ syncCheckStatusLabel.setText("Finished!");
+ updateRepoTree();
+ // TODO: Label einfärben
+ // TODO: Enable Tree Checkboxes
+ syncDownloadButton.setEnabled(true);
+ syncAddedFilesLabel.setText(String.valueOf(fileChecker.getAddedCount()));
+ syncChangedFilesLabel.setText(String.valueOf(fileChecker.getChangedCount()));
+ syncDeletedFilesLabel.setText(String.valueOf(fileChecker.getDeletedCount()));
- model.add(new Modset("--Server", Modset.Type.CLIENT, null, false));
+ syncSizeLabel.setText(String.valueOf(fileChecker.getSize())); // TODO: Make Humanreadable
+ } else if (s.equals("fileCheckerStopped")) {
+ syncCheckButton.setEnabled(true);
+ syncCheckAbortButton.setEnabled(false);
+ syncCheckProgress.setValue(0);
+ syncCheckStatusLabel.setText("Failed!");
- Modset.MODSET_LIST.forEach((name, set) -> {
- model.add(set);
- });
- });
+ syncAddedFilesLabel.setText("" + 0);
+ syncChangedFilesLabel.setText("" + 0);
+ syncDeletedFilesLabel.setText("" + 0);
+
+ syncSizeLabel.setText("0.0 B");
}
}
}
diff --git a/src/main/java/de/mc8051/arma3launcher/interfaces/Observable.java b/src/main/java/de/mc8051/arma3launcher/interfaces/Observable.java
index dc6784e..3a13096 100644
--- a/src/main/java/de/mc8051/arma3launcher/interfaces/Observable.java
+++ b/src/main/java/de/mc8051/arma3launcher/interfaces/Observable.java
@@ -7,5 +7,5 @@ public interface Observable {
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
- public void notifyObservers(Object obj);
+ public void notifyObservers(String obj);
}
diff --git a/src/main/java/de/mc8051/arma3launcher/interfaces/Observer.java b/src/main/java/de/mc8051/arma3launcher/interfaces/Observer.java
index 7a3fb76..474d280 100644
--- a/src/main/java/de/mc8051/arma3launcher/interfaces/Observer.java
+++ b/src/main/java/de/mc8051/arma3launcher/interfaces/Observer.java
@@ -5,5 +5,5 @@ package de.mc8051.arma3launcher.interfaces;
*/
public interface Observer {
- public void update(Object o);
+ public void update(String o);
}
diff --git a/src/main/java/de/mc8051/arma3launcher/model/JCheckBoxTree.java b/src/main/java/de/mc8051/arma3launcher/model/JCheckBoxTree.java
new file mode 100644
index 0000000..a45eafc
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/model/JCheckBoxTree.java
@@ -0,0 +1,295 @@
+package de.mc8051.arma3launcher.model;
+
+import javax.swing.*;
+import javax.swing.event.EventListenerList;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeSelectionModel;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * Created by SomethingSomething https://stackoverflow.com/a/21851201/5605489
+ */
+public class JCheckBoxTree extends JTree {
+
+ private static final long serialVersionUID = -4194122328392241790L;
+
+ JCheckBoxTree selfPointer = this;
+
+
+ // 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 isSelected;
+ boolean hasChildren;
+ boolean allChildrenSelected;
+
+ public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
+ isSelected = isSelected_;
+ hasChildren = hasChildren_;
+ allChildrenSelected = allChildrenSelected_;
+ }
+ }
+
+ HashMap nodesCheckingState;
+ HashSet checkedPaths = new HashSet();
+
+ // Defining a new event type for the checking mechanism and preparing event-handling mechanism
+ protected EventListenerList listenerList = new EventListenerList();
+
+ public class CheckChangeEvent extends EventObject {
+ private static final long serialVersionUID = -8100230309044193368L;
+
+ public CheckChangeEvent(Object source) {
+ super(source);
+ }
+ }
+
+ public interface CheckChangeEventListener extends EventListener {
+ public void checkStateChanged(CheckChangeEvent event);
+ }
+
+ public void addCheckChangeEventListener(CheckChangeEventListener listener) {
+ listenerList.add(CheckChangeEventListener.class, listener);
+ }
+
+ public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
+ listenerList.remove(CheckChangeEventListener.class, listener);
+ }
+
+ public void clearCheckChangeEventListeners() {
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = listeners.length - 1; i >= 0; i--) {
+ if (listeners[i] == CheckChangeEventListener.class) {
+ listenerList.remove(CheckChangeEventListener.class, ((CheckChangeEventListener) listeners[i + 1]));
+ }
+ }
+ }
+
+ void fireCheckChangeEvent(CheckChangeEvent evt) {
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = 0; i < listeners.length; i++) {
+ if (listeners[i] == CheckChangeEventListener.class) {
+ ((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
+ }
+ }
+ }
+
+ // Override
+ public void setModel(TreeModel newModel) {
+ super.setModel(newModel);
+ resetCheckingState();
+ }
+
+ public void createModel(Object[] value) {
+ createTreeModel(value);
+ }
+
+ // New method that returns only the checked paths (totally ignores original "selection" mechanism)
+ public TreePath[] getCheckedPaths() {
+ return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
+ }
+
+ // Returns true in case that the node is selected, has children but not all of them are selected
+ public boolean isSelectedPartially(TreePath path) {
+ CheckedNode cn = nodesCheckingState.get(path);
+ return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
+ }
+
+ public void resetCheckingState() {
+ nodesCheckingState = new HashMap();
+ checkedPaths = new HashSet();
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
+ if (node == null) {
+ return;
+ }
+ addSubtreeToCheckingStateTracking(node);
+ }
+
+ // Creating data structure of the current model for the checking mechanism
+ private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) {
+ TreeNode[] path = node.getPath();
+ TreePath tp = new TreePath(path);
+ CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
+ nodesCheckingState.put(tp, cn);
+ for (int i = 0; i < node.getChildCount(); i++) {
+ addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
+ }
+ }
+
+ // Overriding cell renderer by a class that ignores the original "selection" mechanism
+ // It decides how to show the nodes due to the checking-mechanism
+ private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer {
+ private static final long serialVersionUID = -7341833835878991719L;
+ JCheckBox checkBox;
+
+ public CheckBoxCellRenderer() {
+ super();
+ this.setLayout(new BorderLayout());
+ checkBox = new JCheckBox();
+ add(checkBox, BorderLayout.CENTER);
+ setOpaque(false);
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent(JTree tree, Object value,
+ boolean selected, boolean expanded, boolean leaf, int row,
+ boolean hasFocus) {
+ checkBox.setText(value.toString());
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
+ TreePath tp = new TreePath(node.getPath());
+ CheckedNode cn = nodesCheckingState.get(tp);
+ if (cn == null) {
+ return this;
+ }
+ checkBox.setSelected(cn.isSelected);
+ checkBox.setOpaque(cn.isSelected && cn.hasChildren && !cn.allChildrenSelected);
+ return this;
+ }
+ }
+
+ public JCheckBoxTree() {
+ super();
+ // Disabling toggling by double-click
+ this.setToggleClickCount(0);
+ // Overriding cell renderer by new one defined above
+ CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
+ this.setCellRenderer(cellRenderer);
+
+ // Overriding selection model by an empty one
+ DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
+ private static final long serialVersionUID = -8190634240451667286L;
+
+ // Totally disabling the selection mechanism
+ public void setSelectionPath(TreePath path) {
+ }
+
+ public void addSelectionPath(TreePath path) {
+ }
+
+ public void removeSelectionPath(TreePath path) {
+ }
+
+ public void setSelectionPaths(TreePath[] pPaths) {
+ }
+ };
+ // Calling checking mechanism on mouse click
+ this.addMouseListener(new MouseListener() {
+ public void mouseClicked(MouseEvent arg0) {
+ TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
+ if (tp == null) {
+ return;
+ }
+ boolean checkMode = !nodesCheckingState.get(tp).isSelected;
+ checkSubTree(tp, checkMode);
+ updatePredecessorsWithCheckMode(tp, checkMode);
+ // Firing the check change event
+ fireCheckChangeEvent(new CheckChangeEvent(new Object()));
+ // Repainting tree after the data structures were updated
+ selfPointer.repaint();
+ }
+
+ public void mouseEntered(MouseEvent arg0) {
+ }
+
+ public void mouseExited(MouseEvent arg0) {
+ }
+
+ public void mousePressed(MouseEvent arg0) {
+ }
+
+ public void mouseReleased(MouseEvent arg0) {
+ }
+ });
+ this.setSelectionModel(dtsm);
+ }
+
+ // When a node is checked/unchecked, updating the states of the predecessors
+ protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
+ TreePath parentPath = tp.getParentPath();
+ // If it is the root, stop the recursive calls and return
+ if (parentPath == null) {
+ return;
+ }
+ CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
+ DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
+ parentCheckedNode.allChildrenSelected = true;
+ parentCheckedNode.isSelected = false;
+ for (int i = 0; i < parentNode.getChildCount(); i++) {
+ TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
+ CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
+ // It is enough that even one subtree is not fully selected
+ // to determine that the parent is not fully selected
+ if (!childCheckedNode.allChildrenSelected) {
+ parentCheckedNode.allChildrenSelected = false;
+ }
+ // If at least one child is selected, selecting also the parent
+ if (childCheckedNode.isSelected) {
+ parentCheckedNode.isSelected = true;
+ }
+ }
+ if (parentCheckedNode.isSelected) {
+ checkedPaths.add(parentPath);
+ } else {
+ checkedPaths.remove(parentPath);
+ }
+ // Go to upper predecessor
+ updatePredecessorsWithCheckMode(parentPath, check);
+ }
+
+ // Recursively checks/unchecks a subtree
+ protected void checkSubTree(TreePath tp, boolean check) {
+ CheckedNode cn = nodesCheckingState.get(tp);
+ cn.isSelected = check;
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
+ for (int i = 0; i < node.getChildCount(); i++) {
+ checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
+ }
+ cn.allChildrenSelected = check;
+ if (check) {
+ checkedPaths.add(tp);
+ } else {
+ checkedPaths.remove(tp);
+ }
+ }
+
+ public void expandAllNodes() {
+ setTreeExpandedState(true);
+ }
+
+ public void collapseAllNodes() {
+ setTreeExpandedState(false);
+ }
+
+ private void setTreeExpandedState(boolean expanded) {
+ DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
+ setNodeExpandedState( node, expanded);
+ }
+
+ private void setNodeExpandedState(DefaultMutableTreeNode node, boolean expanded) {
+ ArrayList list = Collections.list(node.children());
+ for (TreeNode treeNode : list) {
+ setNodeExpandedState((DefaultMutableTreeNode)treeNode, expanded);
+ }
+ if (!expanded && node.isRoot()) {
+ return;
+ }
+ TreePath path = new TreePath(node.getPath());
+ if (expanded) {
+ expandPath(path);
+ } else {
+ collapsePath(path);
+ }
+ }
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/objects/AbstractMod.java b/src/main/java/de/mc8051/arma3launcher/objects/AbstractMod.java
new file mode 100644
index 0000000..d5fe685
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/objects/AbstractMod.java
@@ -0,0 +1,9 @@
+package de.mc8051.arma3launcher.objects;
+
+/**
+ * Created by gurkengewuerz.de on 25.03.2020.
+ */
+public interface AbstractMod {
+
+ public String getName();
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/objects/Mod.java b/src/main/java/de/mc8051/arma3launcher/objects/Mod.java
index c77e953..8cd32a7 100644
--- a/src/main/java/de/mc8051/arma3launcher/objects/Mod.java
+++ b/src/main/java/de/mc8051/arma3launcher/objects/Mod.java
@@ -1,17 +1,40 @@
package de.mc8051.arma3launcher.objects;
+import java.util.ArrayList;
+
/**
* Created by gurkengewuerz.de on 25.03.2020.
*/
-public class Mod {
+public class Mod implements AbstractMod {
private String name;
+ private long size;
+ private ArrayList files;
public Mod(String name) {
+ this(name, -1, new ArrayList<>());
+ }
+
+ public Mod(String name, long size, ArrayList files) {
this.name = name;
+ this.size = size;
+ this.files = files;
+ }
+
+ public ArrayList getFiles() {
+ return files;
+ }
+
+ public long getSize() {
+ return size;
}
public String getName() {
return name;
}
+
+
+ public Mod clone() {
+ return new Mod(name, size, new ArrayList<>(files));
+ }
}
diff --git a/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java b/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java
new file mode 100644
index 0000000..8d0176c
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/objects/ModFile.java
@@ -0,0 +1,82 @@
+package de.mc8051.arma3launcher.objects;
+
+import org.apache.commons.io.FilenameUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Created by gurkengewuerz.de on 25.03.2020.
+ */
+public class ModFile implements AbstractMod {
+
+ private File f;
+ private long size;
+ private String folder;
+ private String filename;
+ private String extension;
+ private String modfileString;
+
+ public ModFile(File f, String modfile, long size) {
+ // File: Abosolut Path
+ // modfile: addons/config/something.pbo
+ // size: size as in metafile on server
+ this.f = f;
+ this.size = size;
+ this.folder = FilenameUtils.getPath(modfile);
+ this.filename = FilenameUtils.getBaseName(modfile);
+ this.extension = FilenameUtils.getExtension(modfile);
+ this.modfileString = modfile;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public String getReletaivePath() {
+ return folder;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+
+ public ArrayList getPath() {
+ ArrayList list = new ArrayList<>();
+ 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;
+ return f.length();
+ }
+
+ public boolean exists() {
+ if(!f.exists() || !f.isFile()) return false;
+ return true;
+ }
+
+ public String getName() {
+ return filename + (extension.equals("") ? "" : "." + extension);
+ }
+
+ public String getModfileString() {
+ return modfileString;
+ }
+
+ public File getLocaleFile() {
+ return f;
+ }
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/objects/Modset.java b/src/main/java/de/mc8051/arma3launcher/objects/Modset.java
index 40e271e..1df8f63 100644
--- a/src/main/java/de/mc8051/arma3launcher/objects/Modset.java
+++ b/src/main/java/de/mc8051/arma3launcher/objects/Modset.java
@@ -56,6 +56,10 @@ public class Modset {
return type;
}
+ public void play() {
+ // TODO: Implement play with this Modset
+ }
+
public static enum Type {
SERVER,
CLIENT
diff --git a/src/main/java/de/mc8051/arma3launcher/repo/DownloadStatus.java b/src/main/java/de/mc8051/arma3launcher/repo/DownloadStatus.java
new file mode 100644
index 0000000..f7900fb
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/repo/DownloadStatus.java
@@ -0,0 +1,10 @@
+package de.mc8051.arma3launcher.repo;
+
+/**
+ * Created by gurkengewuerz.de on 25.03.2020.
+ */
+public enum DownloadStatus {
+ RUNNING,
+ FINNISHED,
+ ERROR;
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java b/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java
new file mode 100644
index 0000000..a030744
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/repo/FileChecker.java
@@ -0,0 +1,203 @@
+package de.mc8051.arma3launcher.repo;
+
+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.Mod;
+import de.mc8051.arma3launcher.objects.ModFile;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Created by gurkengewuerz.de on 26.03.2020.
+ */
+public class FileChecker implements Observable {
+
+ private List observerList = new ArrayList<>();
+ private JProgressBar pb;
+ private boolean stop = false;
+
+ ArrayList deleted = new ArrayList<>();
+ HashMap> changed = new HashMap<>();
+ int changedCount = 0;
+ HashMap> added = new HashMap<>();
+ int addedCount = 0;
+
+ long size = 0;
+
+ public FileChecker(JProgressBar pb) {
+ this.pb = pb;
+ }
+
+ public void check() {
+ deleted.clear();
+ changed.clear();
+ changedCount = 0;
+ added.clear();
+ addedCount = 0;
+ size = 0;
+
+ int i = 0;
+ SwingUtilities.invokeLater(() -> {
+ pb.setMaximum(RepositoryManger.MOD_LIST_SIZE);
+ pb.setValue(0);
+ });
+
+ for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
+ if(stop) {
+ stop = false;
+ notifyObservers("fileCheckerStopped");
+ return;
+ }
+ if(abstractMod instanceof Mod) {
+ Mod m = (Mod) abstractMod;
+
+ for (ModFile mf : m.getFiles()) {
+ checkFile(mf.getName(), mf);
+ i++;
+ int finalI = i;
+ SwingUtilities.invokeLater(() -> {
+ pb.setValue(finalI);
+ });
+
+ if(stop) {
+ stop = false;
+ notifyObservers("fileCheckerStopped");
+ return;
+ }
+ }
+ } else if (abstractMod instanceof ModFile) {
+ ModFile mf = (ModFile) abstractMod;
+ checkFile(mf.getName(), mf);
+ i++;
+ int finalI1 = i;
+ SwingUtilities.invokeLater(() -> {
+ pb.setValue(finalI1);
+ });
+ }
+ }
+
+ checkDeleted();
+ notifyObservers("fileChecker");
+ }
+
+ public void stop() {
+ stop = true;
+ }
+
+ private void checkFile(String mod, ModFile mf) {
+ // TODO: Add mf to Array if Array already exists
+ if(!mf.exists()) {
+ added.put(mod, mf);
+ addedCount++;
+ size += mf.getSize();
+ return;
+ }
+
+ if(mf.getLocalSize() != mf.getSize()) {
+ changed.put(mod, mf);
+ changedCount++;
+ size += mf.getSize();
+ return;
+ }
+ }
+
+ private void checkDeleted() {
+ String modPath = ArmA3Launcher.user_config.get("client", "modPath");
+ if(modPath == null) modPath = "";
+
+ try {
+ List filePathList = Files.find(Paths.get(modPath),
+ Integer.MAX_VALUE,
+ (filePath, fileAttr) -> fileAttr.isRegularFile())
+ .collect(Collectors.toList());
+
+
+ for (Path localPath : filePathList) {
+ ModFile deleteable = null;
+
+ outerloop:
+ for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
+ if(abstractMod instanceof Mod) {
+ Mod m = (Mod) abstractMod;
+
+ for (ModFile mf : m.getFiles()) {
+ if (mf.getLocaleFile().getPath().equals(localPath.toString())) {
+ deleteable = mf;
+ break outerloop;
+ }
+ }
+ } else if (abstractMod instanceof ModFile) {
+ ModFile mf = (ModFile) abstractMod;
+ if (mf.getLocaleFile().getPath().equals(localPath.toString())) {
+ deleteable = mf;
+ break outerloop;
+ }
+ }
+ }
+
+ if (deleteable == null) {
+ deleted.add(localPath);
+ }
+ }
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ public ArrayList getDeleted() {
+ return deleted;
+ }
+
+ public HashMap> getChanged() {
+ return changed;
+ }
+
+ public HashMap> getAdded() {
+ return added;
+ }
+
+ public int getDeletedCount() {
+ return deleted.size();
+ }
+
+ public int getChangedCount() {
+ return changedCount;
+ }
+
+ public int getAddedCount() {
+ return addedCount;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ @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);
+ }
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java b/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java
index d4a11ee..68f09bb 100644
--- a/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java
+++ b/src/main/java/de/mc8051/arma3launcher/repo/RepositoryManger.java
@@ -3,6 +3,9 @@ package de.mc8051.arma3launcher.repo;
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.Mod;
+import de.mc8051.arma3launcher.objects.ModFile;
import de.mc8051.arma3launcher.objects.Modset;
import de.mc8051.arma3launcher.objects.Server;
import de.mc8051.arma3launcher.utils.Callback;
@@ -12,8 +15,11 @@ import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -24,19 +30,49 @@ import java.util.logging.Logger;
public class RepositoryManger implements Observable {
private static RepositoryManger instance;
+
+ public static ArrayList MOD_LIST = new ArrayList<>();
+ public static int MOD_LIST_SIZE = 0;
+ private static HashMap statusMap = new HashMap<>();
+
private List observerList = new ArrayList<>();
private OkHttpClient client = new OkHttpClient();
private RepositoryManger() {
+ statusMap.put(Type.METADATA, DownloadStatus.FINNISHED);
+ statusMap.put(Type.MODSET, DownloadStatus.FINNISHED);
+ }
+
+ private void getAsync(String url, Callback.HttpCallback callback) {
+ new Thread(() -> {
+ try {
+ Request request = new Request.Builder()
+ .url(url)
+ .build();
+
+ Response r = client.newCall(request).execute();
+ if (!r.isSuccessful()) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Cant open " + r.request().url().toString() + " code " + r.code());
+ return;
+ }
+
+ callback.response(r);
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
+ callback.response(null);
+ }
+ }).start();
}
public void refreshMeta() {
- downloadMeta(new Callback.HttpCallback() {
+ statusMap.replace(Type.METADATA, DownloadStatus.RUNNING);
+ RepositoryManger.getInstance().notifyObservers(Type.METADATA.toString());
+ getAsync(ArmA3Launcher.config.getString("sync.url") + "/.sync/server.json", new Callback.HttpCallback() {
@Override
public void response(Response r) {
if (!r.isSuccessful()) {
- Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Cant open " + r.request().url().toString() + " code " + r.code());
- RepositoryManger.getInstance().notifyObservers("refreshMetaFailed");
+ statusMap.replace(Type.METADATA, DownloadStatus.ERROR);
+ RepositoryManger.getInstance().notifyObservers(Type.METADATA.toString());
return;
}
@@ -44,6 +80,7 @@ public class RepositoryManger implements Observable {
JSONObject jsonObject = new JSONObject(r.body().string());
if (jsonObject.has("modsets")) {
+ Modset.MODSET_LIST.clear();
JSONArray modsets = jsonObject.getJSONArray("modsets");
if (modsets.length() > 0) {
for (int i = 0; i < modsets.length(); i++) {
@@ -55,6 +92,7 @@ public class RepositoryManger implements Observable {
// Init servers after modsets because server search preset string in modsets
if (jsonObject.has("servers")) {
+ Server.SERVER_LIST.clear();
JSONArray servers = jsonObject.getJSONArray("servers");
if (servers.length() > 0) {
for (int i = 0; i < servers.length(); i++) {
@@ -64,7 +102,8 @@ public class RepositoryManger implements Observable {
}
}
- RepositoryManger.getInstance().notifyObservers("refreshMeta");
+ statusMap.replace(Type.METADATA, DownloadStatus.FINNISHED);
+ RepositoryManger.getInstance().notifyObservers(Type.METADATA.toString());
} catch (IOException | NullPointerException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
}
@@ -72,19 +111,68 @@ public class RepositoryManger implements Observable {
});
}
- private void downloadMeta(Callback.HttpCallback callback) {
- new Thread(() -> {
- try {
- Request request = new Request.Builder()
- .url(ArmA3Launcher.config.getString("sync.url") + "/.sync/server.json")
- .build();
+ public void refreshModset() {
+ statusMap.replace(Type.MODSET, DownloadStatus.RUNNING);
+ RepositoryManger.getInstance().notifyObservers(Type.MODSET.toString());
+ getAsync(ArmA3Launcher.config.getString("sync.url") + "/.sync/modset.json", new Callback.HttpCallback() {
+ @Override
+ public void response(Response r) {
+ if (!r.isSuccessful()) {
+ statusMap.replace(Type.MODSET, DownloadStatus.ERROR);
+ RepositoryManger.getInstance().notifyObservers(Type.MODSET.toString());
+ return;
+ }
- callback.response(client.newCall(request).execute());
- } catch (IOException e) {
- Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
- callback.response(null);
+ try {
+ RepositoryManger.MOD_LIST.clear();
+ RepositoryManger.MOD_LIST_SIZE = 0;
+ JSONObject jsonObject = new JSONObject(r.body().string());
+
+ String modPath = ArmA3Launcher.user_config.get("client", "modPath");
+ if(modPath == null) modPath = "";
+
+ String finalModPath = modPath;
+ jsonObject.keySet().forEach(modname ->
+ {
+ Object keyvalue = jsonObject.get(modname);
+
+ if (!(keyvalue instanceof JSONObject)) return;
+
+ JSONObject jsonMod = (JSONObject)keyvalue;
+ if(!jsonMod.has("size")) return;
+
+ long modsize = jsonMod.getLong("size");
+
+ if(jsonMod.has("content")) {
+ // Mod Directory
+ JSONObject content = jsonMod.getJSONObject("content");
+
+ ArrayList modFiles = new ArrayList<>();
+ Iterator keys = content.keys();
+ while (keys.hasNext()) {
+ String modfile = keys.next();
+ long modfilesize = content.getLong(modfile);
+
+ modFiles.add(new ModFile(new File(finalModPath + File.separator + modname + File.separator + modfile), modfile, modfilesize));
+ 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));
+ RepositoryManger.MOD_LIST_SIZE++;
+ }
+
+ });
+
+ statusMap.replace(Type.MODSET, DownloadStatus.FINNISHED);
+ RepositoryManger.getInstance().notifyObservers(Type.MODSET.toString());
+ } catch (IOException | NullPointerException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
+ }
}
- }).start();
+ });
}
public static RepositoryManger getInstance() {
@@ -92,6 +180,10 @@ public class RepositoryManger implements Observable {
return instance;
}
+ public DownloadStatus getStatus(Type type) {
+ return statusMap.get(type);
+ }
+
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
@@ -103,7 +195,24 @@ public class RepositoryManger implements Observable {
}
@Override
- public void notifyObservers(Object obj) {
+ public void notifyObservers(String obj) {
for (Observer obs : observerList) obs.update(obj);
}
+
+ public enum Type {
+
+ METADATA("metadata"),
+ MODSET("modset");
+
+ private String modset;
+
+ Type(String modset) {
+ this.modset = modset;
+ }
+
+ @Override
+ public String toString() {
+ return modset;
+ }
+ }
}
diff --git a/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java b/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java
new file mode 100644
index 0000000..0b058f4
--- /dev/null
+++ b/src/main/java/de/mc8051/arma3launcher/repo/Syncer.java
@@ -0,0 +1,8 @@
+package de.mc8051.arma3launcher.repo;
+
+/**
+ * Created by gurkengewuerz.de on 25.03.2020.
+ */
+public class Syncer {
+// FilenameUtils.directoryContains
+}
diff --git a/src/main/java/de/mc8051/arma3launcher/steam/SteamTimer.java b/src/main/java/de/mc8051/arma3launcher/steam/SteamTimer.java
index f66ac31..c874fed 100644
--- a/src/main/java/de/mc8051/arma3launcher/steam/SteamTimer.java
+++ b/src/main/java/de/mc8051/arma3launcher/steam/SteamTimer.java
@@ -2,10 +2,12 @@ package de.mc8051.arma3launcher.steam;
import de.mc8051.arma3launcher.LauncherGUI;
import de.mc8051.arma3launcher.SteamUtils;
+import de.mc8051.arma3launcher.interfaces.Observer;
import de.ralleytn.simple.registry.Key;
import de.ralleytn.simple.registry.Registry;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -15,24 +17,22 @@ import java.util.logging.Logger;
*/
public class SteamTimer extends TimerTask {
+ private static ArrayList observers = new ArrayList<>();
public static boolean steam_running = false;
public static boolean arma_running = false;
- private LauncherGUI gui;
-
- public SteamTimer(LauncherGUI gui) {
- this.gui = gui;
- }
-
@Override
public void run() {
String OS = System.getProperty("os.name").toUpperCase();
if (!OS.contains("WIN")) return;
+ boolean old_steamrunning = steam_running;
+ boolean old_arma_running = arma_running;
+
try {
if(!SteamUtils.findProcess("steam.exe")) {
steam_running = false;
- gui.updateLabels(steam_running, arma_running);
+ if(old_steamrunning != steam_running) notifyObservers("steamtimer");
return;
}
@@ -42,19 +42,28 @@ public class SteamTimer extends TimerTask {
if(activeSteamUser.equals("0x0")) {
steam_running = false;
- gui.updateLabels(steam_running, arma_running);
+ if(old_steamrunning != steam_running) notifyObservers("steamtimer");
return;
}
steam_running = true;
arma_running = SteamUtils.findProcess("arma3.exe") || SteamUtils.findProcess("arma3_x64.exe") || SteamUtils.findProcess("arma3launcher.exe");
+
+ if(old_steamrunning != steam_running || old_arma_running != arma_running) notifyObservers("steamtimer");
} catch (IOException e) {
steam_running = false;
arma_running = false;
+ notifyObservers("steamtimer");
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
}
+ }
- gui.updateLabels(steam_running, arma_running);
+ public static void addObserver(Observer observer) {
+ observers.add(observer);
+ }
+
+ public void notifyObservers(String obj) {
+ for(Observer o : observers) o.update(obj);
}
}
diff --git a/src/main/resources/lang_de_DE.properties b/src/main/resources/lang_de_DE.properties
index 7ad2911..88ef780 100644
--- a/src/main/resources/lang_de_DE.properties
+++ b/src/main/resources/lang_de_DE.properties
@@ -1,3 +1,4 @@
+arma_running=ArmA läuft noch. Bitte beende ArmA.
abort=Abbrechen
arm33_parameter=ArmA 3 Startparameter
arma3_installpath=ArmA 3 Installationspfad
@@ -65,7 +66,7 @@ showscripterrors_desc=Fehler in Scripten werden in einem Hinweistext signalisier
signed_in=eingeloggt
speed=Geschwindigkeit
speed_up_game=Spielstart beschleunigen
-spikintro_desc=Die Intro-Sequenz wird übersprungen.
+skipintro_desc=Die Intro-Sequenz wird übersprungen.
total_file_size=Gesamtgröße
update=Update
use64bitclient_desc=Startet Arma3 mit der für 64-Bit Betriebssysteme optimierten .exe-Datei (kann die Performance von Arma 3 verbessern und Probleme beheben (bspw. 3-FPS-Bug).
@@ -79,4 +80,14 @@ du sie deaktivieren und ohne diese Option erneut syncen.\
\
Ebenfalls könnte es zu kurzen Performance einbußen kommen.
window_desc=Ist diese Option aktiv, wird Arma 3 im Fenstermodus gestartet.
-world_desc=Hier kann eine Karte eingetragen werden, die geladen und in den Menüs als Hintergrund angezeigt werden soll (z.B. „altis“ oder „stratis“ – ohne Anführungszeichen!). Ist das Feld leer, wird keine Karte während des Startens geladen und der Start von Arma 3 ist entsprechend schneller.
\ No newline at end of file
+world_desc=Hier kann eine Karte eingetragen werden, die geladen und in den Menüs als Hintergrund angezeigt werden soll (z.B. „altis“ oder „stratis“ – ohne Anführungszeichen!). Ist das Feld leer, wird keine Karte während des Startens geladen und der Start von Arma 3 ist entsprechend schneller.
+path_not_set=ArmA oder Mod Verzeichnis nicht gesetzt
+steam_not_running=Steam läuft nicht. Bitte starte Steam.
+update_repository=Repository aktualisieren
+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
+deleted_files=Gelöschte Dateien
\ No newline at end of file
diff --git a/src/main/resources/lang_en_US.properties b/src/main/resources/lang_en_US.properties
index 42a61c2..52d559a 100644
--- a/src/main/resources/lang_en_US.properties
+++ b/src/main/resources/lang_en_US.properties
@@ -1,6 +1,7 @@
abort=Abort
arm33_parameter=ArmA 3 Start parameters
arma3_installpath=ArmA 3 Installation path
+arma_running=ArmA is still running. Please exit ArmA.
backend_url=Backend URL
behaviour_aafter_start=Behaviour after start
beta_desc=Data from subdirectories can be started as well.
@@ -64,7 +65,7 @@ showscripterrors_desc=Errors in scripts are signaled in a message text.
signed_in=signed in
speed=Speed
speed_up_game=Speed up game start
-spikintro_desc=The intro sequence is skipped.
+skipintro_desc=The intro sequence is skipped.
total_file_size=Total file size
update=Update
use64bitclient_desc=Starts Arma3 with the .exe file optimized for 64-bit operating systems (can improve the performance of Arma 3 and fix problems (e.g. 3-FPS bug)
@@ -78,4 +79,10 @@ you deactivate it and sync again without this option.\
\
This could also lead to short performance losses.
window_desc=If this option is active, Arma 3 is started in windowed mode.
-world_desc=Here you can enter a card to be loaded and displayed as background in the menus (e.g. "altis" or "stratis" - without quotation marks!). If the field is empty, no map will be loaded during startup and the start of Arma 3 will be faster.
\ No newline at end of file
+world_desc=Here you can enter a card to be loaded and displayed as background in the menus (e.g. "altis" or "stratis" - without quotation marks!). If the field is empty, no map will be loaded during startup and the start of Arma 3 will be faster.
+path_not_set=ArmA or Mod directory not set
+steam_not_running=Steam not running. Please start Steam.
+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
\ No newline at end of file