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