diff --git a/README.md b/README.md
new file mode 100644
index 0000000..73f49c4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# CMPDL
+
+**Curse Modpack Downloader**
+
+Downloads modpacks from curse, given a URL. Experimental. Forked from Vazkii's project with a new UI.
+
+Licensed under the [WTFPL](http://www.wtfpl.net/). Have fun.
+
+## Usage Instructions
+
+ * Download CMPDL on the [Releases](https://github.com/Franckyi/CMPDL/releases) page. Run it.
+ * Find the pack you want. We'll use [Proton](https://minecraft.curseforge.com/projects/proton) as an example.
+ * Copy the URL of the project home on CurseForge. It should look like the one on the last point. Paste this URL on the "Modpack URL" field on CMPDL.
+ * To specify a version of the modpack, enter that version in the "File ID" field
+ * Choose the modpack destination. If you use MultiMC, choose a new folder under the "instances" folder.
+ * Press "Start Download" and wait.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..ed9c534
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ com.github.franckyi
+ cmpdl
+ 2.0.0-b1
+
+ 1.8
+ 1.8
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.1
+
+
+
+
+
+ maven-assembly-plugin
+
+
+
+ com.github.franckyi.cmpdl.CMPDL
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/github/franckyi/cmpdl/CMPDL.java b/src/main/java/com/github/franckyi/cmpdl/CMPDL.java
new file mode 100644
index 0000000..c7b018c
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/CMPDL.java
@@ -0,0 +1,89 @@
+package com.github.franckyi.cmpdl;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+
+public class CMPDL extends Application {
+
+ private static final String NAME = "Curse Modpack Downloader";
+ private static final String VERSION = "2.0.0-b1";
+ private static final String AUTHOR = "Franckyi (original version by Vazkii)";
+
+ public static String title() {
+ return String.format("%s v%s by %s", NAME, VERSION, AUTHOR);
+ }
+
+ public static InterfaceController controller;
+ public static Parent parent;
+ public static Stage stage;
+ public static Gson gson;
+
+ public static String path;
+ public static String zipFileName;
+
+ public static final Set exceptions = new HashSet<>();
+
+ public void start(Stage primaryStage) throws Exception {
+ stage = primaryStage;
+ gson = new GsonBuilder().create();
+ URL interface0 = getClass().getClassLoader().getResource("interface.fxml");
+ if (interface0 != null) {
+ FXMLLoader loader = new FXMLLoader(interface0);
+ parent = loader.load();
+ controller = loader.getController();
+ stage.setScene(new Scene(parent));
+ stage.setResizable(false);
+ stage.setTitle(title());
+ stage.show();
+ } else {
+ throw new RuntimeException("Impossible to load interface.fxml file : the application can't start");
+ }
+ }
+
+ @Override
+ public void stop() throws Exception {
+ controller.stop(null);
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+
+ public static String getTempDirectory() {
+ return path + File.separator + ".cmpdl_temp";
+ }
+
+ public static String getZipFile() {
+ return getTempDirectory() + File.separator + zipFileName;
+ }
+
+ private static String getMinecraftDirectory() {
+ return path + File.separator + "minecraft";
+ }
+
+ public static String getModsDirectory() {
+ return getMinecraftDirectory() + File.separator + "mods";
+ }
+
+ public static String getInstanceFile() {
+ return path + File.separator + "instance.cfg";
+ }
+
+ public static String getManifestFile() {
+ return getTempDirectory() + File.separator + "manifest.json";
+ }
+
+ public static String toOverridePath(File file, String override) {
+ return file.getPath().replace(".cmpdl_temp" + File.separator + override, "minecraft");
+ }
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/InterfaceController.java b/src/main/java/com/github/franckyi/cmpdl/InterfaceController.java
new file mode 100644
index 0000000..0253cd7
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/InterfaceController.java
@@ -0,0 +1,264 @@
+package com.github.franckyi.cmpdl;
+
+
+import com.github.franckyi.cmpdl.task.CleanTask;
+import com.github.franckyi.cmpdl.task.DownloadModpackTask;
+import com.github.franckyi.cmpdl.task.VerifyProjectTask;
+import javafx.concurrent.Task;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.*;
+import javafx.scene.input.Clipboard;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.stage.DirectoryChooser;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+@SuppressWarnings("Duplicates")
+public class InterfaceController implements Initializable {
+
+ private boolean dark;
+ private Optional verified = Optional.empty();
+ private Task> primaryTask, secondaryTask;
+
+ @Override
+ public void initialize(URL location, ResourceBundle resources) {
+ log(CMPDL.title());
+ log("Java version " + System.getProperty("java.version"));
+ destinationPath.setText(System.getProperty("user.home") + File.separator + "modpack");
+ }
+
+ @FXML
+ private TextField modpackURL;
+
+ @FXML
+ private TextField fileID;
+
+ @FXML
+ public TextField destinationPath;
+
+ @FXML
+ private TextArea logTextArea;
+
+ @FXML
+ private ProgressBar primaryBar;
+
+ @FXML
+ private ProgressBar secondaryBar;
+
+ @FXML
+ private ProgressIndicator primaryIndicator;
+
+ @FXML
+ private ProgressIndicator secondaryIndicator;
+
+ @FXML
+ private Label primaryLabel;
+
+ @FXML
+ private Label secondaryLabel;
+
+ @FXML
+ private Button verifyButton;
+
+ @FXML
+ private Button destinationButton;
+
+ @FXML
+ private Button downloadButton;
+
+ @FXML
+ private Button stopButton;
+
+ @FXML
+ void chooseDestination(ActionEvent event) {
+ DirectoryChooser directoryChooser = new DirectoryChooser();
+ File dir = new File(destinationPath.getText());
+ directoryChooser.setInitialDirectory(dir.exists() ? dir : new File(System.getProperty("user.home")));
+ directoryChooser.setTitle("Choose the output game / instance directory :");
+ File file = directoryChooser.showDialog(CMPDL.stage);
+ if (file != null) {
+ destinationPath.setText(file.getAbsolutePath());
+ log("Destination : " + file.getAbsolutePath());
+ }
+ }
+
+ @FXML
+ void logClear(ActionEvent event) {
+ logTextArea.setText("");
+ }
+
+ @FXML
+ void logCopy(ActionEvent event) {
+ ClipboardContent content = new ClipboardContent();
+ content.putString(logTextArea.getText());
+ Clipboard.getSystemClipboard().setContent(content);
+ }
+
+ @FXML
+ void startDownload(ActionEvent event) {
+ setDisableButtons(true);
+ if (!verified.isPresent()) {
+ downloadButton.setText("Verifying...");
+ VerifyProjectTask task = new VerifyProjectTask(fileID.getText(), modpackURL.getText());
+ task.setOnSucceeded(e -> {
+ log(task.getValue().isPresent() ? "The modpack is valid !" : "The modpack is invalid !");
+ if (task.getValue().isPresent()) {
+ verified = task.getValue();
+ startDownload0();
+ } else {
+ setDisableButtons(false);
+ downloadButton.setText("Start Download");
+ }
+ });
+ new Thread(task).start();
+ } else startDownload0();
+ }
+
+ private void startDownload0() {
+ log("Download starting !");
+ downloadButton.setText("Downloading...");
+ stopButton.setDisable(false);
+ verified.ifPresent(s -> {
+ try {
+ CMPDL.path = destinationPath.getText();
+ CMPDL.zipFileName = new File(new URL(s).getFile()).getName().replaceAll("%20", " ");
+ setPrimaryProgress(new DownloadModpackTask(s), "Step 1/3 : Downloading modpack");
+ new Thread(primaryTask).start();
+ } catch (IOException e) {
+ trace(e);
+ }
+ });
+ }
+
+ @FXML
+ void verify(ActionEvent event) {
+ verifyButton.setText("Verifying...");
+ setDisableButtons(true);
+ VerifyProjectTask task = new VerifyProjectTask(fileID.getText(), modpackURL.getText());
+ task.setOnSucceeded(e -> {
+ log(task.getValue().isPresent() ? "The modpack is valid !" : "The modpack is invalid !");
+ if (task.getValue().isPresent()) {
+ verified = task.getValue();
+ verifyButton.setText("Verified !");
+ setDisableButtons(false);
+ verifyButton.setDisable(true);
+ } else {
+ verifyButton.setText("Verify");
+ setDisableButtons(false);
+ }
+ });
+ new Thread(task).start();
+ }
+
+ @FXML
+ void input(KeyEvent keyEvent) {
+ verified = Optional.empty();
+ verifyButton.setText("Verify");
+ verifyButton.setDisable(false);
+ }
+
+ @FXML
+ void changeTheme(MouseEvent mouseEvent) {
+ CMPDL.parent.setStyle(dark ? "" : "-fx-base: rgba(60, 63, 65, 255);");
+ dark = !dark;
+ }
+
+ public void log(String text) {
+ logTextArea.appendText(LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yy HH:mm:ss > ")) + text + "\n");
+ }
+
+ public void trace(Throwable t) {
+ logTextArea.appendText(t.toString() + "\n");
+ for (StackTraceElement traceElement : t.getStackTrace())
+ logTextArea.appendText("\tat " + traceElement + "\n");
+ if (t.getCause() != null) {
+ trace(t.getCause());
+ }
+ }
+
+ private void setDisableButtons(boolean value) {
+ downloadButton.setDisable(value);
+ verifyButton.setDisable(value);
+ destinationButton.setDisable(value);
+ modpackURL.setEditable(value);
+ fileID.setEditable(value);
+ destinationPath.setEditable(value);
+ }
+
+ public void setPrimaryProgress(Task> task, String text) {
+ primaryTask = task;
+ primaryBar.progressProperty().bind(task.progressProperty());
+ primaryIndicator.progressProperty().bind(task.progressProperty());
+ primaryLabel.setText(text);
+ }
+
+ private void resetPrimaryProgress() {
+ primaryTask = null;
+ primaryBar.progressProperty().unbind();
+ primaryBar.setProgress(0);
+ primaryIndicator.progressProperty().unbind();
+ primaryIndicator.setProgress(0);
+ primaryLabel.setText("");
+ }
+
+ public void setSecondaryProgress(Task> task, String text) {
+ secondaryTask = task;
+ secondaryBar.progressProperty().bind(task.progressProperty());
+ secondaryIndicator.progressProperty().bind(task.progressProperty());
+ secondaryLabel.setText(text);
+ }
+
+ private void resetSecondaryProgress() {
+ secondaryTask = null;
+ secondaryBar.progressProperty().unbind();
+ secondaryBar.setProgress(0);
+ secondaryIndicator.progressProperty().unbind();
+ secondaryIndicator.setProgress(0);
+ secondaryLabel.setText("");
+ }
+
+ @FXML
+ void stop(ActionEvent actionEvent) {
+ if (primaryTask != null) {
+ if (primaryTask.isDone() || primaryTask.cancel()) {
+ log("Primary task cancelled");
+ resetPrimaryProgress();
+ } else
+ log("Couldn't cancel primary task");
+
+ }
+ if (secondaryTask != null) {
+ if (secondaryTask.isDone() || secondaryTask.cancel()) {
+ log("Secondary task cancelled");
+ resetSecondaryProgress();
+ } else
+ log("Couldn't cancel secondary task");
+ }
+ if ((primaryTask == null || primaryTask.isCancelled() || primaryTask.isDone()) && (secondaryTask == null || secondaryTask.isCancelled() || secondaryTask.isDone())) {
+ setDisableButtons(false);
+ log("Execution stopped !");
+ new Thread(CleanTask::new).start();
+ downloadButton.setText("Start Download");
+ }
+ }
+
+ public void reset() {
+ CMPDL.exceptions.clear();
+ resetPrimaryProgress();
+ resetSecondaryProgress();
+ setDisableButtons(false);
+ stopButton.setDisable(true);
+ downloadButton.setText("Start Download");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/franckyi/cmpdl/ManifestJson.java b/src/main/java/com/github/franckyi/cmpdl/ManifestJson.java
new file mode 100644
index 0000000..d1db733
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/ManifestJson.java
@@ -0,0 +1,51 @@
+package com.github.franckyi.cmpdl;
+
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class ManifestJson {
+
+ public MinecraftJson minecraft;
+
+ public String getForgeVersion() {
+ for (MinecraftJson.ModloaderJson loader : minecraft.modLoaders) {
+ if (loader.id.startsWith("forge"))
+ return loader.id.substring("forge-".length());
+ }
+ return "N/A";
+ }
+
+ @SuppressWarnings("unused")
+ public class MinecraftJson {
+
+ public String version;
+ public List modLoaders;
+
+ @SuppressWarnings("unused")
+ public class ModloaderJson {
+
+ public String id;
+ public boolean primary;
+
+ }
+ }
+
+ public String manifestType;
+ public int manifestVersion;
+ public String name;
+ public String version;
+ public String author;
+ public List files;
+
+ @SuppressWarnings("unused")
+ public class FileJson {
+
+ public int projectID;
+ public int fileID;
+ public boolean required;
+
+ }
+
+ public String overrides;
+
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/CleanTask.java b/src/main/java/com/github/franckyi/cmpdl/task/CleanTask.java
new file mode 100644
index 0000000..80e6571
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/CleanTask.java
@@ -0,0 +1,43 @@
+package com.github.franckyi.cmpdl.task;
+
+import com.github.franckyi.cmpdl.CMPDL;
+import com.github.franckyi.cmpdl.ManifestJson;
+
+import java.io.File;
+import java.util.Arrays;
+
+public class CleanTask extends CustomTask {
+
+ private ManifestJson manifest;
+
+ public CleanTask(ManifestJson manifest) {
+ this.manifest = manifest;
+ }
+
+ public CleanTask() {
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ log("> Cleaning up...");
+ del(new File(CMPDL.getTempDirectory()));
+ return null;
+ }
+
+ @Override
+ protected void succeeded() {
+ if (manifest != null) {
+ log("### RECOMMENDED FORGE VERSION : " + manifest.getForgeVersion() + ". ###");
+ log("### A newer version should also work.");
+ log("### You must install it manually if you're using MultiMC ! ###");
+ }
+ getController().reset();
+ log(CMPDL.exceptions.isEmpty() ? "Done !" : "Done with " + CMPDL.exceptions.size() + " error(s). See log for more info.");
+ log("----------------");
+ }
+
+ private void del(File file) {
+ if (file.isDirectory()) Arrays.asList(file.listFiles()).forEach(this::del);
+ file.delete();
+ }
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/CopyOverridesTask.java b/src/main/java/com/github/franckyi/cmpdl/task/CopyOverridesTask.java
new file mode 100644
index 0000000..8f5cee8
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/CopyOverridesTask.java
@@ -0,0 +1,111 @@
+package com.github.franckyi.cmpdl.task;
+
+import com.github.franckyi.cmpdl.CMPDL;
+import com.github.franckyi.cmpdl.ManifestJson;
+import javafx.application.Platform;
+
+import java.io.*;
+import java.util.Arrays;
+
+public class CopyOverridesTask extends CustomTask {
+
+ private final ManifestJson manifest;
+ private final File dir;
+ private long curSize = 0L;
+ private final long maxSize;
+
+ public CopyOverridesTask(ManifestJson manifest, File folder) {
+ this.manifest = manifest;
+ this.dir = new File(folder + File.separator + manifest.overrides);
+ this.maxSize = dir.length();
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ log("> Copying overrides");
+ if (dir.isDirectory()) {
+ for (File file : dir.listFiles()) {
+ if (!processFile(file)) return null;
+ }
+ }
+ File cfg = new File(CMPDL.getInstanceFile());
+ if (!cfg.exists())
+ cfg.createNewFile();
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(cfg))) {
+ writer.write("InstanceType=OneSix\n"
+ + "IntendedVersion=" + manifest.minecraft.version + "\n"
+ + "LogPrePostOutput=true\n"
+ + "OverrideCommands=false\n"
+ + "OverrideConsole=false\n"
+ + "OverrideJavaArgs=false\n"
+ + "OverrideJavaLocation=false\n"
+ + "OverrideMemory=false\n"
+ + "OverrideWindow=false\n"
+ + "iconKey=default\n"
+ + "lastLaunchTime=0\n"
+ + "name=" + manifest.name + " " + manifest.version + "\n"
+ + "notes=Modpack by " + manifest.author + ". Generated by CMPDL. Using Forge " + manifest.getForgeVersion() + ".\n"
+ + "totalTimePlayed=0\n");
+ }
+ return null;
+ }
+
+ private boolean processFile(File file) {
+ if (file.isDirectory()) {
+ Arrays.asList(file.listFiles()).forEach(this::processFile);
+ } else {
+ File dest = new File(CMPDL.toOverridePath(file, manifest.overrides));
+ new File(dest.getPath().replace(dest.getName(), "")).mkdirs();
+ try {
+ dest.createNewFile();
+ } catch (IOException e) {
+ trace(e);
+ e.printStackTrace();
+ }
+ CopyOverrideTask task = new CopyOverrideTask(file, dest);
+ Platform.runLater(() -> getController().setSecondaryProgress(task, file.getName()));
+ new Thread(task).start();
+ while (!task.isDone() && !task.isCancelled()) {
+ if (task.isCancelled()) return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void succeeded() {
+ log("> Done copying overrides");
+ new Thread(new CleanTask(manifest)).start();
+ }
+
+ private class CopyOverrideTask extends CustomTask {
+
+ private final File source;
+ private final File dest;
+
+ public CopyOverrideTask(File source, File dest) {
+ this.source = source;
+ this.dest = dest;
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ try (FileInputStream in = new FileInputStream(source)) {
+ FileOutputStream out = new FileOutputStream(dest);
+ long curSize2 = 0L;
+ long maxSize2 = source.length();
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = in.read(buffer)) > 0) {
+ out.write(buffer, 0, length);
+ updateProgress(curSize2 += length, maxSize2);
+ CopyOverridesTask.this.updateProgress(curSize += curSize2, maxSize);
+ }
+ out.close();
+ }
+ log("> > Copied " + dest.getName());
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/CustomTask.java b/src/main/java/com/github/franckyi/cmpdl/task/CustomTask.java
new file mode 100644
index 0000000..63260a4
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/CustomTask.java
@@ -0,0 +1,58 @@
+package com.github.franckyi.cmpdl.task;
+
+import com.github.franckyi.cmpdl.CMPDL;
+import com.github.franckyi.cmpdl.InterfaceController;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+abstract class CustomTask extends Task {
+
+ @Override
+ protected V call() throws Exception {
+ try {
+ return call0();
+ } catch (Exception e) {
+ e.printStackTrace();
+ CMPDL.exceptions.add(e);
+ CMPDL.controller.trace(e);
+ }
+ return null;
+ }
+
+ protected abstract V call0() throws Exception;
+
+ void log(String s) {
+ Platform.runLater(() -> getController().log(s));
+ }
+
+ void trace(Throwable t) {
+ Platform.runLater(() -> getController().trace(t));
+ }
+
+ InterfaceController getController() {
+ return CMPDL.controller;
+ }
+
+ String crawl(String url) throws IOException, IllegalArgumentException {
+ String location = getLocation(url);
+ return location != null ? crawl(location) : url;
+ }
+
+ String crawlAddHost(String url) throws IOException, IllegalArgumentException {
+ URL url0 = new URL(url);
+ String location = getLocation(url);
+ return location != null ? location.contains("://") ? crawlAddHost(location) : crawl(url0.getProtocol() + "://" + url0.getHost() + location) : url;
+ }
+
+ private String getLocation(String url) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ connection.setInstanceFollowRedirects(false);
+ if (connection.getResponseCode() < 200 || connection.getResponseCode() >= 400)
+ throw new IOException("Error " + connection.getResponseCode() + " at " + url);
+ return connection.getHeaderField("location");
+ }
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/DownloadModpackTask.java b/src/main/java/com/github/franckyi/cmpdl/task/DownloadModpackTask.java
new file mode 100644
index 0000000..0244533
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/DownloadModpackTask.java
@@ -0,0 +1,53 @@
+package com.github.franckyi.cmpdl.task;
+
+import com.github.franckyi.cmpdl.CMPDL;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class DownloadModpackTask extends CustomTask {
+
+ private URL url;
+ private File tmp;
+
+ public DownloadModpackTask(String url) throws IOException {
+ this.url = new URL(url);
+ new File(CMPDL.getTempDirectory()).mkdirs();
+ this.tmp = new File(CMPDL.getZipFile());
+ this.tmp.createNewFile();
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ log("> Downloading modpack at " + url);
+ this.updateProgress(0, 1);
+ HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
+ long completeFileSize = httpConnection.getContentLength();
+ BufferedInputStream bis = new BufferedInputStream(httpConnection.getInputStream());
+ FileOutputStream fis = new FileOutputStream(tmp);
+ byte[] buffer = new byte[1024];
+ long dl = 0;
+ int count;
+ while ((count = bis.read(buffer, 0, 1024)) != -1 && !isCancelled()) {
+ fis.write(buffer, 0, count);
+ dl += count;
+ this.updateProgress(dl, completeFileSize);
+ }
+ fis.close();
+ bis.close();
+ return null;
+ }
+
+ @Override
+ protected void succeeded() {
+ UnzipModpackTask task = new UnzipModpackTask(tmp);
+ getController().setSecondaryProgress(task, "Unzipping modpack");
+ new Thread(task).start();
+ log("> Download succeeded !");
+ }
+
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/DownloadModsTask.java b/src/main/java/com/github/franckyi/cmpdl/task/DownloadModsTask.java
new file mode 100644
index 0000000..d3687ce
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/DownloadModsTask.java
@@ -0,0 +1,93 @@
+package com.github.franckyi.cmpdl.task;
+
+import com.github.franckyi.cmpdl.CMPDL;
+import com.github.franckyi.cmpdl.ManifestJson;
+import javafx.application.Platform;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class DownloadModsTask extends CustomTask {
+
+ private final File folder;
+ private ManifestJson manifest;
+
+ DownloadModsTask(File folder) {
+ new File(CMPDL.getModsDirectory()).mkdirs();
+ this.folder = folder;
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ updateProgress(0, 1);
+ manifest = CMPDL.gson.fromJson(new FileReader(CMPDL.getManifestFile()), ManifestJson.class);
+ int length = manifest.files.size();
+ log("### " + manifest.name + " v" + manifest.version + " by " + manifest.author + " ###");
+ log("> Mods download started : " + length + " files found");
+ final int[] i = {0};
+ for (ManifestJson.FileJson fileJson : manifest.files) {
+ if (!isCancelled()) {
+ log("> > Resolving " + fileJson.projectID + ":" + fileJson.fileID + " (" + (i[0]++) + "/" + length + ")");
+ DownloadModTask task = new DownloadModTask(fileJson);
+ task.setOnSucceeded(event -> updateProgress(i[0] - 1, length));
+ new Thread(task).start();
+ while (!task.isDone() && !task.isCancelled()) {
+ if (task.isCancelled()) return null;
+ }
+ } else return null;
+ }
+ return null;
+ }
+
+ @Override
+ protected void succeeded() {
+ log("> Mods download succeeded !");
+ CopyOverridesTask task = new CopyOverridesTask(manifest, folder);
+ getController().setPrimaryProgress(task, "Step 3/3 : Copying overrides");
+ new Thread(task).start();
+ }
+
+ private class DownloadModTask extends CustomTask {
+
+ private final ManifestJson.FileJson fileJson;
+
+ DownloadModTask(ManifestJson.FileJson fileJson) {
+ this.fileJson = fileJson;
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ updateProgress(0, 1);
+ String url0 = crawl(crawlAddHost("http://minecraft.curseforge.com/projects/" + fileJson.projectID) + "/files/" + fileJson.fileID + "/download");
+ if (DownloadModsTask.this.isCancelled()) return null;
+ URL url = new URL(url0);
+ String fileName = new File(url.getFile()).getName().replaceAll("%20", " ");
+ log("> > > Downloading " + fileName);
+ Platform.runLater(() -> getController().setSecondaryProgress(this, fileName));
+ HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
+ long completeFileSize = httpConnection.getContentLength();
+ BufferedInputStream bis = new BufferedInputStream(httpConnection.getInputStream());
+ File file = new File(CMPDL.getModsDirectory() + File.separator + fileName);
+ file.createNewFile();
+ FileOutputStream fos = new FileOutputStream(file);
+ byte[] buffer = new byte[1024];
+ long dl = 0;
+ int count;
+ while ((count = bis.read(buffer, 0, 1024)) != -1 && !isCancelled()) {
+ fos.write(buffer, 0, count);
+ dl += count;
+ this.updateProgress(dl, completeFileSize);
+ }
+ fos.close();
+ bis.close();
+ log("> > > Download succeeded !");
+ return null;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/UnzipModpackTask.java b/src/main/java/com/github/franckyi/cmpdl/task/UnzipModpackTask.java
new file mode 100644
index 0000000..68edfcb
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/UnzipModpackTask.java
@@ -0,0 +1,61 @@
+package com.github.franckyi.cmpdl.task;
+
+import java.io.*;
+import java.nio.channels.FileChannel;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class UnzipModpackTask extends CustomTask {
+
+ private final File zipfile;
+ private final File folder;
+
+ UnzipModpackTask(File file) {
+ this.zipfile = file;
+ this.folder = file.getParentFile();
+ }
+
+ @Override
+ protected Void call0() throws Exception {
+ log("> Unzipping file at " + folder.getAbsolutePath());
+ FileInputStream is = new FileInputStream(zipfile.getCanonicalFile());
+ FileChannel channel = is.getChannel();
+ ZipEntry ze;
+ try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is))) {
+ while ((ze = zis.getNextEntry()) != null && !isCancelled()) {
+ File f = new File(folder.getCanonicalPath(), ze.getName());
+ if (ze.isDirectory()) {
+ f.mkdirs();
+ continue;
+ }
+ f.getParentFile().mkdirs();
+ try {
+ try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) {
+ final byte[] buf = new byte[1024];
+ int bytesRead;
+ long nread = 0L;
+ long length = zipfile.length();
+ while (-1 != (bytesRead = zis.read(buf)) && !isCancelled()) {
+ fos.write(buf, 0, bytesRead);
+ nread += bytesRead;
+ updateProgress(channel.position(), length);
+ }
+ }
+ } catch (final IOException ioe) {
+ f.delete();
+ throw ioe;
+ }
+ }
+ }
+ updateProgress(0, 0);
+ return null;
+ }
+
+ @Override
+ protected void succeeded() {
+ DownloadModsTask task = new DownloadModsTask(folder);
+ getController().setPrimaryProgress(task, "Step 2/3 : Downloading dependencies");
+ new Thread(task).start();
+ log("> Unzipping succeeded !");
+ }
+}
diff --git a/src/main/java/com/github/franckyi/cmpdl/task/VerifyProjectTask.java b/src/main/java/com/github/franckyi/cmpdl/task/VerifyProjectTask.java
new file mode 100644
index 0000000..f864e55
--- /dev/null
+++ b/src/main/java/com/github/franckyi/cmpdl/task/VerifyProjectTask.java
@@ -0,0 +1,56 @@
+package com.github.franckyi.cmpdl.task;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Optional;
+
+public class VerifyProjectTask extends CustomTask> {
+
+ private String fileID;
+ private final String modpackURL;
+
+ public VerifyProjectTask(String fileID, String modpackURL) {
+ this.fileID = fileID;
+ this.modpackURL = modpackURL;
+ }
+
+ @Override
+ protected Optional call0() throws Exception {
+ log("Verifying " + modpackURL + ":" + fileID);
+ String protocol, host, folder;
+ boolean protocolValid, hostValid, folderValid, isCurse;
+ URL url;
+ try {
+ url = new URL(modpackURL);
+ } catch (MalformedURLException e) {
+ log("The URL is malformed. This will not work !");
+ return Optional.empty();
+ }
+ protocol = url.getProtocol();
+ host = url.getHost();
+ folder = url.getFile();
+ isCurse = host.equals("mods.curse.com");
+ protocolValid = Arrays.asList("http", "https").contains(protocol);
+ hostValid = Arrays.asList("mods.curse.com", "minecraft.curseforge.com", "www.feed-the-beast.com").contains(host);
+ folderValid = isCurse ? folder.startsWith("/modpacks/minecraft/") && folder.split("/").length == 4 : folder.startsWith("/projects/") && folder.split("/").length == 3;
+ if (!protocolValid || !hostValid || !folderValid) {
+ log("The URL is incorrect !" + (!protocolValid ? "\n- Protocol invalid" : "") + (!hostValid ? "\n- Host invalid" : "") + (!folderValid ? "\n- Folder invalid" : ""));
+ return Optional.empty();
+ }
+ if (fileID.isEmpty()) {
+ fileID = "latest";
+ }
+ if (host.equals("www.feed-the-beast.com") && fileID.equals("latest")) {
+ log("You must specify a file ID other than\n'latest' for host www.feed-the-beast.com.");
+ return Optional.empty();
+ }
+ try {
+ return Optional.of(crawl(isCurse ? modpackURL + "/" + (fileID.equals("latest") ? "download" : fileID) : modpackURL + "/files/" + fileID + (fileID.equals("latest") ? "" : "/download")));
+ } catch (IOException e) {
+ log("The file doesn't exist or website is unreachable !");
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/resources/interface.fxml b/src/main/resources/interface.fxml
new file mode 100644
index 0000000..be48f96
--- /dev/null
+++ b/src/main/resources/interface.fxml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+