From b7267c611f77e42803131c2e9fce15f5581c2e37 Mon Sep 17 00:00:00 2001 From: Franckyi Date: Sat, 7 Apr 2018 15:24:12 +0200 Subject: [PATCH] Finished UI and tasks Fixed #1, #4 and #5 --- pom.xml | 11 +- .../java/com/github/franckyi/cmpdl/CMPDL.java | 65 ++++-- .../github/franckyi/cmpdl/CurseMetaAPI.java | 60 ++++- .../github/franckyi/cmpdl/EnumContent.java | 5 - .../franckyi/cmpdl/controller/CleanTask.java | 39 ++++ .../controller/DestinationPaneController.java | 122 ++++++++++- .../cmpdl/controller/FilePaneController.java | 118 +++++++++- .../cmpdl/controller/IContentController.java | 17 ++ .../controller/MainWindowController.java | 50 +++-- .../controller/ModpackPaneController.java | 74 ++++--- .../controller/ProgressPaneController.java | 207 +++++++++++++++++- .../cmpdl/core/ContentControllerView.java | 13 ++ .../cmpdl/{ => core}/ControllerView.java | 11 +- .../franckyi/cmpdl/model/IProjectFile.java | 35 +++ .../franckyi/cmpdl/model/ModpackManifest.java | 84 +++++++ .../github/franckyi/cmpdl/model/Project.java | 85 +++++++ .../franckyi/cmpdl/model/ProjectFile.java | 50 +++++ .../cmpdl/model/ProjectFileMinimal.java | 34 +++ .../cmpdl/model/ProjectFilesList.java | 31 +++ .../github/franckyi/cmpdl/task/TaskBase.java | 24 ++ .../task/cursemeta/GetProjectFileTask.java | 21 ++ .../task/cursemeta/GetProjectFilesTask.java | 20 ++ .../task/cursemeta/GetProjectIdTask.java | 45 ++++ .../cmpdl/task/cursemeta/GetProjectTask.java | 20 ++ .../task/mpimport/CopyOverridesTask.java | 61 ++++++ .../cmpdl/task/mpimport/DownloadFileTask.java | 46 ++++ .../cmpdl/task/mpimport/DownloadModsTask.java | 58 +++++ .../cmpdl/task/mpimport/ReadManifestTask.java | 24 ++ .../cmpdl/task/mpimport/UnzipFileTask.java | 54 +++++ .../cmpdl/view/ProjectFileMinimalView.java | 37 ++++ src/main/resources/DestinationPane.fxml | 86 +++++++- src/main/resources/FilePane.fxml | 58 ++++- src/main/resources/MainWindow.fxml | 4 +- src/main/resources/ProgressPane.fxml | 63 +++++- 34 files changed, 1619 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/com/github/franckyi/cmpdl/EnumContent.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/controller/CleanTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/controller/IContentController.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/core/ContentControllerView.java rename src/main/java/com/github/franckyi/cmpdl/{ => core}/ControllerView.java (64%) create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/IProjectFile.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/ModpackManifest.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/Project.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/ProjectFile.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/ProjectFileMinimal.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/model/ProjectFilesList.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/TaskBase.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFileTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFilesTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectIdTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/mpimport/CopyOverridesTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadFileTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadModsTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/mpimport/ReadManifestTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/task/mpimport/UnzipFileTask.java create mode 100644 src/main/java/com/github/franckyi/cmpdl/view/ProjectFileMinimalView.java diff --git a/pom.xml b/pom.xml index 6aa9dfe..44689f7 100644 --- a/pom.xml +++ b/pom.xml @@ -13,9 +13,14 @@ - com.mashape.unirest - unirest-java - 1.4.9 + org.json + json + 20180130 + + + com.squareup.okhttp3 + okhttp + 3.10.0 org.jsoup diff --git a/src/main/java/com/github/franckyi/cmpdl/CMPDL.java b/src/main/java/com/github/franckyi/cmpdl/CMPDL.java index 81ab219..cb46f67 100644 --- a/src/main/java/com/github/franckyi/cmpdl/CMPDL.java +++ b/src/main/java/com/github/franckyi/cmpdl/CMPDL.java @@ -1,11 +1,22 @@ package com.github.franckyi.cmpdl; import com.github.franckyi.cmpdl.controller.*; -import com.mashape.unirest.http.Unirest; +import com.github.franckyi.cmpdl.core.ContentControllerView; +import com.github.franckyi.cmpdl.core.ControllerView; import javafx.application.Application; +import javafx.application.Platform; import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import javafx.stage.Stage; +import java.awt.*; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class CMPDL extends Application { public static final String NAME = "CMPDL"; @@ -13,34 +24,56 @@ public class CMPDL extends Application { public static final String AUTHOR = "Franckyi"; public static final String TITLE = String.format("%s v%s by %s", NAME, VERSION, AUTHOR); + public static final String USER_AGENT = String.format("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0 %s/%s (%s)", NAME, VERSION, AUTHOR); + + public static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); + public static Stage stage; + public static Scene scene; public static ControllerView mainWindow; - public static ControllerView modpackPane; - public static ControllerView filePane; - public static ControllerView destinationPane; - public static ControllerView progressPane; + public static ContentControllerView modpackPane; + public static ContentControllerView filePane; + public static ContentControllerView destinationPane; + public static ContentControllerView progressPane; + + public static ContentControllerView currentContent; @Override public void start(Stage primaryStage) throws Exception { - modpackPane = new ControllerView<>("ModpackPane.fxml", EnumContent.MODPACK); - filePane = new ControllerView<>("FilePane.fxml", EnumContent.FILE); - destinationPane = new ControllerView<>("DestinationPane.fxml", EnumContent.DESTINATION); - progressPane = new ControllerView<>("ProgressPane.fxml", EnumContent.PROGRESS); - mainWindow = new ControllerView<>("MainWindow.fxml"); stage = primaryStage; - stage.setScene(new Scene(mainWindow.getView())); + modpackPane = new ContentControllerView<>("ModpackPane.fxml"); + filePane = new ContentControllerView<>("FilePane.fxml"); + destinationPane = new ContentControllerView<>("DestinationPane.fxml"); + progressPane = new ContentControllerView<>("ProgressPane.fxml"); + mainWindow = new ControllerView<>("MainWindow.fxml"); + scene = new Scene(mainWindow.getView()); + stage.setScene(scene); stage.setTitle(TITLE); stage.show(); } public static void main(String[] args) { - Unirest.clearDefaultHeaders(); - Unirest.setDefaultHeader("User-Agent", String.format("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0 %s/%s (%s)", NAME, VERSION, AUTHOR)); - if (args.length == 0) { - launch(args); + launch(args); + } + + @Override + public void stop() { + EXECUTOR_SERVICE.shutdown(); + } + + public static void openBrowser(String url) { + if (Desktop.isDesktopSupported()) { + EXECUTOR_SERVICE.execute(() -> { + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (IOException | URISyntaxException e) { + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Can't open URL", ButtonType.OK).show()); + e.printStackTrace(); + } + }); } else { - // DO SOMETHING + new Alert(Alert.AlertType.ERROR, "Desktop not supported", ButtonType.OK).show(); } } } diff --git a/src/main/java/com/github/franckyi/cmpdl/CurseMetaAPI.java b/src/main/java/com/github/franckyi/cmpdl/CurseMetaAPI.java index c65fa8c..8b8c326 100644 --- a/src/main/java/com/github/franckyi/cmpdl/CurseMetaAPI.java +++ b/src/main/java/com/github/franckyi/cmpdl/CurseMetaAPI.java @@ -1,33 +1,71 @@ package com.github.franckyi.cmpdl; -import com.mashape.unirest.http.JsonNode; -import com.mashape.unirest.http.Unirest; -import com.mashape.unirest.http.exceptions.UnirestException; +import com.github.franckyi.cmpdl.model.Project; +import com.github.franckyi.cmpdl.model.ProjectFile; +import com.github.franckyi.cmpdl.model.ProjectFilesList; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; + public class CurseMetaAPI { + private static final OkHttpClient CLIENT = new OkHttpClient(); private static final String URL = "http://cursemeta.dries007.net/api/v2/direct"; - public static JSONObject getProject(int projectId) throws UnirestException { - return get("/GetAddOn", projectId).getObject(); + public static Project getProject(int projectId) { + try { + return new Project(new JSONObject(get("/GetAddOn", projectId))); + } catch (JSONException e) { + e.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, String.format("Unknown project (%d)", projectId), ButtonType.OK).show()); + return null; + } } - public static JSONArray getProjectFiles(int projectId) throws UnirestException { - return get("/GetAllFilesForAddOn", projectId).getArray(); + public static ProjectFilesList getProjectFiles(int projectId) { + try { + return new ProjectFilesList(new JSONArray(get("/GetAllFilesForAddOn", projectId))); + } catch (JSONException e) { + e.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, String.format("Unknown project (%d)", projectId), ButtonType.OK).show()); + return null; + } } - public static JSONObject getProjectFile(int projectId, int fileId) throws UnirestException { - return get("/GetAddOnFile", projectId, fileId).getObject(); + public static ProjectFile getProjectFile(int projectId, int fileId) { + try { + return new ProjectFile(new JSONObject(get("/GetAddOnFile", projectId, fileId))); + } catch (JSONException e) { + e.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, String.format("Unknown project file (%d:%d)", projectId, fileId), ButtonType.OK).show()); + return null; + } } - private static JsonNode get(String path, Object... args) throws UnirestException { + private static String get(String path, Object... args) { StringBuilder sb = new StringBuilder(URL + path); for (Object o : args) { sb.append("/").append(o); } - return Unirest.get(sb.toString()).asJson().getBody(); + Request request = new Request.Builder().header("User-Agent", CMPDL.USER_AGENT).url(sb.toString()).get().build(); + try (Response response = CLIENT.newCall(request).execute()) { + if (response.body() != null) { + return response.body().string(); + } else { + return ""; + } + } catch (IOException e) { + e.printStackTrace(); + return ""; + } } } diff --git a/src/main/java/com/github/franckyi/cmpdl/EnumContent.java b/src/main/java/com/github/franckyi/cmpdl/EnumContent.java deleted file mode 100644 index 5fcca49..0000000 --- a/src/main/java/com/github/franckyi/cmpdl/EnumContent.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.github.franckyi.cmpdl; - -public enum EnumContent { - MODPACK, FILE, DESTINATION, PROGRESS -} diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/CleanTask.java b/src/main/java/com/github/franckyi/cmpdl/controller/CleanTask.java new file mode 100644 index 0000000..f22e929 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/controller/CleanTask.java @@ -0,0 +1,39 @@ +package com.github.franckyi.cmpdl.controller; + +import com.github.franckyi.cmpdl.task.TaskBase; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class CleanTask extends TaskBase { + + private final File temp; + + public CleanTask(File temp) { + this.temp = temp; + } + + @Override + protected Void call0() throws Throwable { + updateTitle("Cleaning"); + Files.walkFileTree(temp.toPath(), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + return null; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/DestinationPaneController.java b/src/main/java/com/github/franckyi/cmpdl/controller/DestinationPaneController.java index 225e823..ab6f78c 100644 --- a/src/main/java/com/github/franckyi/cmpdl/controller/DestinationPaneController.java +++ b/src/main/java/com/github/franckyi/cmpdl/controller/DestinationPaneController.java @@ -1,4 +1,124 @@ package com.github.franckyi.cmpdl.controller; -public class DestinationPaneController { +import com.github.franckyi.cmpdl.CMPDL; +import com.github.franckyi.cmpdl.model.Project; +import com.github.franckyi.cmpdl.model.ProjectFile; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.stage.DirectoryChooser; + +import java.io.File; +import java.net.URL; +import java.util.ResourceBundle; + +public class DestinationPaneController implements Initializable, IContentController { + + private Project project; + private ProjectFile file; + + @FXML + private ImageView logoImageView; + + @FXML + private Label titleLabel; + + @FXML + private Label authorLabel; + + @FXML + private Label summaryLabel; + + @FXML + private ImageView categoryImageView; + + @FXML + private Label categoryLabel; + + @FXML + private Label fileNameLabel; + + @FXML + private Label mcVersionLabel; + + @FXML + private Label releaseTypeLabel; + + @FXML + private TextField destinationField; + + @FXML + void actionChooseDestination(ActionEvent event) { + DirectoryChooser dc = new DirectoryChooser(); + dc.setTitle("Choose the destination folder :"); + File dst = dc.showDialog(CMPDL.stage); + if (dst != null) { + destinationField.setText(dst.getAbsolutePath()); + } + } + + @FXML + void actionViewInBrowser(ActionEvent event) { + CMPDL.openBrowser(project.getUrl()); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + destinationField.textProperty().addListener((o, oldValue, newValue) -> CMPDL.mainWindow.getController().getStartButton().setDisable(newValue.isEmpty())); + releaseTypeLabel.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize())); + } + + @Override + public void handleNext() { + + } + + @Override + public void handlePrevious() { + CMPDL.mainWindow.getController().setContent(CMPDL.filePane); + CMPDL.mainWindow.getController().getStartButton().setDisable(true); + CMPDL.mainWindow.getController().getNextButton().setDisable(false); + } + + @Override + public void handleStart() { + File dst = new File(destinationField.getText()); + if (dst.isDirectory()) { + if (!dst.exists()) dst.mkdirs(); + if (!dst.canWrite()) { + new Alert(Alert.AlertType.ERROR, "Permission denied. Please choose another destination folder.", ButtonType.OK).show(); + } else { + CMPDL.progressPane.getController().setData(project, file, dst); + CMPDL.mainWindow.getController().setContent(CMPDL.progressPane); + CMPDL.mainWindow.getController().getStartButton().setDisable(true); + CMPDL.mainWindow.getController().getPreviousButton().setDisable(true); + CMPDL.progressPane.getController().downloadModpack(); + } + } else { + new Alert(Alert.AlertType.ERROR, "The destination must be a folder.", ButtonType.OK).show(); + } + } + + public void setProjectAndFile(Project project, ProjectFile file) { + this.project = project; + this.file = file; + logoImageView.setImage(new Image(project.getLogoUrl())); + titleLabel.setText(project.getName()); + authorLabel.setText("by " + project.getAuthor()); + summaryLabel.setText(project.getSummary()); + categoryImageView.setImage(new Image(project.getCategoryLogoUrl())); + categoryLabel.setText(project.getCategoryName()); + fileNameLabel.setText(file.getFileName()); + mcVersionLabel.setText(file.getGameVersion()); + releaseTypeLabel.setText(file.getFileType()); + releaseTypeLabel.setTextFill(file.getColor()); + } } diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/FilePaneController.java b/src/main/java/com/github/franckyi/cmpdl/controller/FilePaneController.java index 0fb7862..478ea5e 100644 --- a/src/main/java/com/github/franckyi/cmpdl/controller/FilePaneController.java +++ b/src/main/java/com/github/franckyi/cmpdl/controller/FilePaneController.java @@ -1,4 +1,120 @@ package com.github.franckyi.cmpdl.controller; -public class FilePaneController { +import com.github.franckyi.cmpdl.CMPDL; +import com.github.franckyi.cmpdl.model.IProjectFile; +import com.github.franckyi.cmpdl.model.Project; +import com.github.franckyi.cmpdl.model.ProjectFilesList; +import com.github.franckyi.cmpdl.task.cursemeta.GetProjectFileTask; +import com.github.franckyi.cmpdl.task.cursemeta.GetProjectFilesTask; +import com.github.franckyi.cmpdl.view.ProjectFileMinimalView; +import javafx.application.Platform; +import javafx.collections.ListChangeListener; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; + +import java.net.URL; +import java.util.ResourceBundle; + +public class FilePaneController implements Initializable, IContentController { + + private Project project; + private ProjectFilesList files = null; + + @FXML + private ImageView logoImageView; + + @FXML + private Label titleLabel; + + @FXML + private Label authorLabel; + + @FXML + private Label summaryLabel; + + @FXML + private ImageView categoryImageView; + + @FXML + private Label categoryLabel; + + @FXML + private ListView filesListView; + + @FXML + private Button changeViewButton; + + public void viewAllFiles() { + if (files != null) { + filesListView.getItems().setAll(files); + changeViewButton.setText("View latest files..."); + changeViewButton.setOnAction(e -> viewLatestFiles()); + } + } + + public void viewLatestFiles() { + filesListView.getItems().setAll(project.getFiles()); + changeViewButton.setText("View all files..."); + changeViewButton.setOnAction(e -> viewAllFiles()); + } + + @FXML + void actionViewInBrowser(ActionEvent event) { + CMPDL.openBrowser(project.getUrl()); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + filesListView.setCellFactory(param -> new ProjectFileMinimalView()); + filesListView.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { + if (CMPDL.currentContent == CMPDL.filePane) { + CMPDL.mainWindow.getController().getNextButton().setDisable(c.getList().size() != 1); + } + }); + } + + public void setProject(Project project) { + this.project = project; + logoImageView.setImage(new Image(project.getLogoUrl())); + titleLabel.setText(project.getName()); + authorLabel.setText("by " + project.getAuthor()); + summaryLabel.setText(project.getSummary()); + categoryImageView.setImage(new Image(project.getCategoryLogoUrl())); + categoryLabel.setText(project.getCategoryName()); + CMPDL.mainWindow.getController().getNextButton().setDisable(true); + CMPDL.mainWindow.getController().getPreviousButton().setDisable(false); + GetProjectFilesTask task = new GetProjectFilesTask(project.getProjectId()); + task.setOnSucceeded(e -> files = task.getValue().orElse(null)); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + @Override + public void handleNext() { + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Loading file data...", ButtonType.CLOSE); + alert.show(); + GetProjectFileTask task = new GetProjectFileTask(project.getProjectId(), filesListView.getSelectionModel().getSelectedItem().getFileId()); + task.setOnSucceeded(e -> Platform.runLater(() -> task.getValue().ifPresent(file -> { + CMPDL.destinationPane.getController().setProjectAndFile(project, file); + CMPDL.mainWindow.getController().setContent(CMPDL.destinationPane); + CMPDL.mainWindow.getController().getNextButton().setDisable(true); + alert.hide(); + }))); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + @Override + public void handlePrevious() { + CMPDL.mainWindow.getController().setContent(CMPDL.modpackPane); + CMPDL.mainWindow.getController().getNextButton().setDisable(false); + CMPDL.mainWindow.getController().getPreviousButton().setDisable(true); + } + + @Override + public void handleStart() { + + } } diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/IContentController.java b/src/main/java/com/github/franckyi/cmpdl/controller/IContentController.java new file mode 100644 index 0000000..1b13aa9 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/controller/IContentController.java @@ -0,0 +1,17 @@ +package com.github.franckyi.cmpdl.controller; + +import javafx.application.Platform; + +public interface IContentController { + + void handleNext(); + + void handlePrevious(); + + default void handleClose() { + Platform.exit(); + } + + void handleStart(); + +} diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/MainWindowController.java b/src/main/java/com/github/franckyi/cmpdl/controller/MainWindowController.java index ba1bc42..eb57b49 100644 --- a/src/main/java/com/github/franckyi/cmpdl/controller/MainWindowController.java +++ b/src/main/java/com/github/franckyi/cmpdl/controller/MainWindowController.java @@ -1,12 +1,12 @@ package com.github.franckyi.cmpdl.controller; import com.github.franckyi.cmpdl.CMPDL; -import com.github.franckyi.cmpdl.ControllerView; -import com.github.franckyi.cmpdl.EnumContent; +import com.github.franckyi.cmpdl.core.ContentControllerView; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import java.net.URL; @@ -14,7 +14,7 @@ public class MainWindowController implements Initializable { - private EnumContent content; + private boolean darkTheme = false; @FXML private AnchorPane contentPane; @@ -29,29 +29,26 @@ public class MainWindowController implements Initializable { private Button nextButton; @FXML - private Button finishButton; + private Button startButton; @FXML void actionClose(ActionEvent event) { - + CMPDL.currentContent.getController().handleClose(); } @FXML - void actionFinish(ActionEvent event) { - + void actionStart(ActionEvent event) { + CMPDL.currentContent.getController().handleStart(); } @FXML void actionNext(ActionEvent event) { - if (content == EnumContent.MODPACK) { - int projectId = CMPDL.modpackPane.getController().getProjectID(); - - } + CMPDL.currentContent.getController().handleNext(); } @FXML void actionPrevious(ActionEvent event) { - + CMPDL.currentContent.getController().handlePrevious(); } @Override @@ -59,10 +56,35 @@ public void initialize(URL location, ResourceBundle resources) { setContent(CMPDL.modpackPane); } - public void setContent(ControllerView cv) { + public void setContent(ContentControllerView cv) { contentPane.getChildren().clear(); contentPane.getChildren().add(cv.getView()); - content = cv.getEnumContent(); + CMPDL.currentContent = cv; + CMPDL.stage.sizeToScene(); + } + + public Button getCloseButton() { + return closeButton; } + public Button getPreviousButton() { + return previousButton; + } + + public Button getNextButton() { + return nextButton; + } + + public Button getStartButton() { + return startButton; + } + + public void switchTheme(MouseEvent mouseEvent) { + if (darkTheme) { + CMPDL.mainWindow.getView().setStyle("-fx-base:#ececec;"); + } else { + CMPDL.mainWindow.getView().setStyle("-fx-base:black;"); + } + darkTheme = !darkTheme; + } } diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/ModpackPaneController.java b/src/main/java/com/github/franckyi/cmpdl/controller/ModpackPaneController.java index f53d735..6162996 100644 --- a/src/main/java/com/github/franckyi/cmpdl/controller/ModpackPaneController.java +++ b/src/main/java/com/github/franckyi/cmpdl/controller/ModpackPaneController.java @@ -1,18 +1,17 @@ package com.github.franckyi.cmpdl.controller; +import com.github.franckyi.cmpdl.CMPDL; +import com.github.franckyi.cmpdl.task.cursemeta.GetProjectIdTask; +import com.github.franckyi.cmpdl.task.cursemeta.GetProjectTask; +import javafx.application.Platform; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; -import org.json.JSONObject; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.util.ResourceBundle; -public class ModpackPaneController implements Initializable { +public class ModpackPaneController implements Initializable, IContentController { @FXML private TextField urlField; @@ -35,35 +34,54 @@ public void initialize(URL location, ResourceBundle resources) { idField.disableProperty().bind(idButton.selectedProperty().not()); } - public int getProjectID() { - int projectId; + @Override + public void handleNext() { if (modpack.getSelectedToggle() == urlButton) { - try { - URL url = new URL(urlField.getText()); - Document doc = Jsoup.parse(url, 10000); - if (url.getHost().equals("www.curseforge.com")) { - projectId = new JSONObject(doc.select("a.download-button").get(0).attr("data-action-value")).getInt("ProjectID"); - } else { - projectId = Integer.parseInt(doc.select("ul.project-details").get(0).child(1).html()); - } - } catch (MalformedURLException e) { - e.printStackTrace(); - new Alert(Alert.AlertType.ERROR, "The URL is malformed", ButtonType.OK).show(); - return -1; - } catch (IOException e) { - e.printStackTrace(); - new Alert(Alert.AlertType.ERROR, "Connection error", ButtonType.OK).show(); - return -1; - } + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Parsing project ID...", ButtonType.CLOSE); + alert.show(); + GetProjectIdTask task = new GetProjectIdTask(urlField.getText()); + task.setOnSucceeded(event -> { + alert.hide(); + handleNext(task.getValue().orElse(-1)); + }); + CMPDL.EXECUTOR_SERVICE.execute(task); } else { + int projectId = -1; try { - projectId = Integer.parseInt(idButton.getText()); + projectId = Integer.parseInt(idField.getText()); } catch (NumberFormatException e) { e.printStackTrace(); new Alert(Alert.AlertType.ERROR, "The project ID must be an integer", ButtonType.OK).show(); - return -1; } + handleNext(projectId); } - return projectId; + } + + @Override + public void handlePrevious() { + + } + + @Override + public void handleStart() { + + } + + public void handleNext(int projectId) { + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Loading project data...", ButtonType.CLOSE); + alert.show(); + GetProjectTask task = new GetProjectTask(projectId); + task.setOnSucceeded(event -> Platform.runLater(() -> task.getValue().ifPresent(project -> { + if (project.isModpack()) { + CMPDL.filePane.getController().setProject(project); + CMPDL.filePane.getController().viewLatestFiles(); + CMPDL.mainWindow.getController().setContent(CMPDL.filePane); + alert.hide(); + } else { + alert.hide(); + new Alert(Alert.AlertType.ERROR, "The project isn't a modpack !", ButtonType.OK).show(); + } + }))); + CMPDL.EXECUTOR_SERVICE.execute(task); } } diff --git a/src/main/java/com/github/franckyi/cmpdl/controller/ProgressPaneController.java b/src/main/java/com/github/franckyi/cmpdl/controller/ProgressPaneController.java index 918a38d..6d30b4b 100644 --- a/src/main/java/com/github/franckyi/cmpdl/controller/ProgressPaneController.java +++ b/src/main/java/com/github/franckyi/cmpdl/controller/ProgressPaneController.java @@ -1,4 +1,209 @@ package com.github.franckyi.cmpdl.controller; -public class ProgressPaneController { +import com.github.franckyi.cmpdl.CMPDL; +import com.github.franckyi.cmpdl.model.ModpackManifest; +import com.github.franckyi.cmpdl.model.Project; +import com.github.franckyi.cmpdl.model.ProjectFile; +import com.github.franckyi.cmpdl.task.mpimport.*; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; + +import java.io.File; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; + +public class ProgressPaneController implements Initializable, IContentController { + + private Task task1, task2; + private boolean done = false; + + private Project project; + private ProjectFile projectFile; + private File destination, zipFile, unzipFolder, minecraft, modsFolder, temp; + private ModpackManifest manifest; + + @FXML + private DialogPane root; + + @FXML + private Label titleLabel; + + @FXML + private Label subLabel1; + + @FXML + private ProgressBar progressBar1; + + @FXML + private ProgressIndicator progressIndicator1; + + @FXML + private Label subLabel2; + + @FXML + private ProgressBar progressBar2; + + @FXML + private ProgressIndicator progressIndicator2; + + @FXML + private TextArea console; + + @Override + public void initialize(URL location, ResourceBundle resources) { + root.expandedProperty().addListener((observable, oldValue, newValue) -> CMPDL.stage.sizeToScene()); + } + + @Override + public void handleNext() { + + } + + @Override + public void handlePrevious() { + + } + + @Override + public void handleStart() { + + } + + @Override + public void handleClose() { + if (!done) { + Optional buttonType = new Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to cancel the modpack download ?", ButtonType.YES, ButtonType.NO).showAndWait(); + if (buttonType.orElse(null) == ButtonType.YES) { + if (task2 != null) task2.cancel(); + if (task1 != null) task1.cancel(); + new CleanTask(temp).run(); + Platform.exit(); + } + } else { + Platform.exit(); + } + } + + public void setData(Project project, ProjectFile projectFile, File destination) { + this.project = project; + this.projectFile = projectFile; + this.destination = destination; + minecraft = new File(destination, "minecraft"); + minecraft.mkdirs(); + modsFolder = new File(minecraft, "mods"); + modsFolder.mkdirs(); + temp = new File(destination, ".cmpdl_temp"); + temp.mkdirs(); + zipFile = new File(temp, projectFile.getFileNameOnDisk()); + unzipFolder = new File(temp, projectFile.getFileNameOnDisk().replace(".zip", "")); + unzipFolder.mkdirs(); + } + + public void setTask1(Task task) { + task1 = task; + bind(task, subLabel1, progressBar1, progressIndicator1); + } + + public void setTask2(Task task) { + task2 = task; + bind(task, subLabel2, progressBar2, progressIndicator2); + } + + private void bind(Task task, Label subLabel, ProgressBar progressBar, ProgressIndicator progressIndicator) { + if (task != null) { + subLabel.textProperty().bind(task.titleProperty()); + progressBar.progressProperty().bind(task.progressProperty()); + progressIndicator.progressProperty().bind(task.progressProperty()); + } else { + subLabel.textProperty().unbind(); + subLabel.setText(""); + progressBar.progressProperty().unbind(); + progressBar.setProgress(0); + progressIndicator.progressProperty().unbind(); + progressIndicator.setProgress(0); + } + } + + public void log(String s, Object... args) { + String s0 = String.format(s, args); + System.out.println(s0); + Platform.runLater(() -> console.appendText(s0 + "\n")); + } + + public void downloadModpack() { + log("Downloading modpack"); + DownloadFileTask task = new DownloadFileTask(projectFile.getDownloadUrl(), new File(temp, projectFile.getFileNameOnDisk())); + task.setOnSucceeded(e -> unzipModpack()); + setTask1(task); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void unzipModpack() { + log("Modpack downloaded successfully"); + log("Unzipping modpack"); + UnzipFileTask task = new UnzipFileTask(zipFile, unzipFolder); + task.setOnSucceeded(e -> readManifest()); + setTask1(task); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void readManifest() { + log("Modpack unzipped successfully"); + log("Reading manifest"); + ReadManifestTask task = new ReadManifestTask(new File(unzipFolder, "manifest.json")); + task.setOnSucceeded(e -> task.getValue().ifPresent(manifest -> { + this.manifest = manifest; + log("### Manifest content :"); + log(manifest.toString()); + downloadMods(); + })); + setTask1(task); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void downloadMods() { + log("Manifest read successfully"); + log("Downloading mods"); + DownloadModsTask task = new DownloadModsTask(modsFolder, manifest.getMods()); + task.setOnSucceeded(e -> copyOverrides()); + setTask1(task); + task.taskProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) setTask2(newValue); + }); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void copyOverrides() { + setTask2(null); + log("Downloaded mods successfully"); + log("Copying overrides"); + CopyOverridesTask task = new CopyOverridesTask(new File(unzipFolder, manifest.getOverrides()), minecraft); + task.setOnSucceeded(e -> clean()); + setTask1(task); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void clean() { + log("Copied overrides successfully"); + log("Cleaning"); + CleanTask task = new CleanTask(temp); + task.setOnSucceeded(e -> finish()); + setTask1(task); + CMPDL.EXECUTOR_SERVICE.execute(task); + } + + private void finish() { + done = true; + setTask1(null); + subLabel1.setText("!!! Modpack imported successfully !!!"); + subLabel2.setText(String.format("Make sure to install %s before playing.", manifest.getForge())); + log("Cleaned successfully"); + log("!!! Modpack imported successfully !!!"); + log("Make sure to install %s before playing.", manifest.getForge()); + } + } diff --git a/src/main/java/com/github/franckyi/cmpdl/core/ContentControllerView.java b/src/main/java/com/github/franckyi/cmpdl/core/ContentControllerView.java new file mode 100644 index 0000000..744da6f --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/core/ContentControllerView.java @@ -0,0 +1,13 @@ +package com.github.franckyi.cmpdl.core; + +import com.github.franckyi.cmpdl.controller.IContentController; + +import java.io.IOException; + +public class ContentControllerView extends ControllerView { + + public ContentControllerView(String res) throws IOException { + super(res); + } + +} diff --git a/src/main/java/com/github/franckyi/cmpdl/ControllerView.java b/src/main/java/com/github/franckyi/cmpdl/core/ControllerView.java similarity index 64% rename from src/main/java/com/github/franckyi/cmpdl/ControllerView.java rename to src/main/java/com/github/franckyi/cmpdl/core/ControllerView.java index 5c250c8..d746418 100644 --- a/src/main/java/com/github/franckyi/cmpdl/ControllerView.java +++ b/src/main/java/com/github/franckyi/cmpdl/core/ControllerView.java @@ -1,4 +1,4 @@ -package com.github.franckyi.cmpdl; +package com.github.franckyi.cmpdl.core; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; @@ -9,7 +9,6 @@ public class ControllerView { private final Parent view; private final T controller; - private EnumContent enumContent; public ControllerView(String res) throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(res)); @@ -17,11 +16,6 @@ public ControllerView(String res) throws IOException { controller = loader.getController(); } - public ControllerView(String res, EnumContent enumContent) throws IOException { - this(res); - this.enumContent = enumContent; - } - public Parent getView() { return view; } @@ -30,7 +24,4 @@ public T getController() { return controller; } - public EnumContent getEnumContent() { - return enumContent; - } } diff --git a/src/main/java/com/github/franckyi/cmpdl/model/IProjectFile.java b/src/main/java/com/github/franckyi/cmpdl/model/IProjectFile.java new file mode 100644 index 0000000..ff66596 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/IProjectFile.java @@ -0,0 +1,35 @@ +package com.github.franckyi.cmpdl.model; + +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.util.Comparator; + +public interface IProjectFile extends Comparable { + + int getFileId(); + + String getFileName(); + + String getGameVersion(); + + String getFileType(); + + @Override + default int compareTo(IProjectFile o) { + return Comparator.comparingInt(IProjectFile::getFileId).reversed().compare(this, o); + } + + default Paint getColor() { + switch (getFileType()) { + case "Alpha": + return Color.web("#E49788"); + case "Beta": + return Color.web("#7FA5C4"); + case "Release": + return Color.web("#8CAF62"); + default: + return Color.BLACK; + } + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/model/ModpackManifest.java b/src/main/java/com/github/franckyi/cmpdl/model/ModpackManifest.java new file mode 100644 index 0000000..3e0cba4 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/ModpackManifest.java @@ -0,0 +1,84 @@ +package com.github.franckyi.cmpdl.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class ModpackManifest { + + private final String name; + private final String author; + private final String version; + private final String mcVersion; + private final String forge; + private final List mods; + private final String overrides; + + public ModpackManifest(JSONObject json) { + name = json.getString("name"); + author = json.getString("author"); + version = json.getString("version"); + mcVersion = json.getJSONObject("minecraft").getString("version"); + forge = json.getJSONObject("minecraft").getJSONArray("modLoaders").getJSONObject(0).getString("id"); + JSONArray array = json.getJSONArray("files"); + mods = new ArrayList<>(array.length()); + for (int i = 0; i < array.length(); i++) { + mods.add(new ModpackManifestMod(array.getJSONObject(i))); + } + overrides = json.getString("overrides"); + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getVersion() { + return version; + } + + public String getMcVersion() { + return mcVersion; + } + + public String getForge() { + return forge; + } + + public List getMods() { + return mods; + } + + public String getOverrides() { + return overrides; + } + + @Override + public String toString() { + return String.format("### %s v%s by %s for MC %s\n### Mod count : %d\n### Forge version required : %s", + name.replaceAll("%", ""), version.replaceAll("%", ""), author.replaceAll("%", ""), mcVersion, mods.size(), forge); + } + + public class ModpackManifestMod { + + private final int projectId, fileId; + + private ModpackManifestMod(JSONObject json) { + projectId = json.getInt("projectID"); + fileId = json.getInt("fileID"); + } + + public int getProjectId() { + return projectId; + } + + public int getFileId() { + return fileId; + } + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/model/Project.java b/src/main/java/com/github/franckyi/cmpdl/model/Project.java new file mode 100644 index 0000000..91bc958 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/Project.java @@ -0,0 +1,85 @@ +package com.github.franckyi.cmpdl.model; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Project { + + private final int projectId; + private final String name; + private final String author; + private final String summary; + private final String logoUrl; + private final String url; + private final String categoryName; + private final String categoryLogoUrl; + private final boolean mod, modpack; + private final List files; + + public Project(JSONObject json) { + projectId = json.getInt("Id"); + name = json.getString("Name"); + author = json.getString("PrimaryAuthorName"); + summary = json.getString("Summary"); + logoUrl = json.getJSONArray("Attachments").getJSONObject(0).getString("Url"); + url = json.getString("WebSiteURL"); + categoryName = json.getString("PrimaryCategoryName"); + categoryLogoUrl = json.getString("PrimaryCategoryAvatarUrl"); + mod = "Mod".equals(json.getString("PackageType")); + modpack = "ModPack".equals(json.getString("PackageType")); + JSONArray array = json.getJSONArray("GameVersionLatestFiles"); + files = new ArrayList<>(array.length()); + for (int i = 0; i < array.length(); i++) { + files.add(new ProjectFileMinimal(array.getJSONObject(i))); + } + Collections.sort(files); + } + + public int getProjectId() { + return projectId; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + public String getSummary() { + return summary; + } + + public String getLogoUrl() { + return logoUrl; + } + + public String getUrl() { + return url; + } + + public String getCategoryName() { + return categoryName; + } + + public String getCategoryLogoUrl() { + return categoryLogoUrl; + } + + public boolean isMod() { + return mod; + } + + public boolean isModpack() { + return modpack; + } + + public List getFiles() { + return files; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/model/ProjectFile.java b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFile.java new file mode 100644 index 0000000..fb27706 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFile.java @@ -0,0 +1,50 @@ +package com.github.franckyi.cmpdl.model; + +import org.json.JSONObject; + +public class ProjectFile implements IProjectFile { + + private final String fileName; + private final String fileNameOnDisk; + private final String gameVersion; + private final String fileType; + private final int fileId; + private final String downloadUrl; + + public ProjectFile(JSONObject json) { + fileName = json.getString("FileName"); + fileNameOnDisk = json.getString("FileNameOnDisk"); + gameVersion = json.getJSONArray("GameVersion").getString(0); + fileType = json.getString("ReleaseType"); + fileId = json.getInt("Id"); + downloadUrl = json.getString("DownloadURL"); + } + + @Override + public int getFileId() { + return fileId; + } + + @Override + public String getFileName() { + return fileName; + } + + public String getFileNameOnDisk() { + return fileNameOnDisk; + } + + @Override + public String getGameVersion() { + return gameVersion; + } + + @Override + public String getFileType() { + return fileType; + } + + public String getDownloadUrl() { + return downloadUrl; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/model/ProjectFileMinimal.java b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFileMinimal.java new file mode 100644 index 0000000..20a418a --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFileMinimal.java @@ -0,0 +1,34 @@ +package com.github.franckyi.cmpdl.model; + +import org.json.JSONObject; + +public class ProjectFileMinimal implements IProjectFile { + + private final String fileName; + private final String gameVersion; + private final String fileType; + private final int fileId; + + public ProjectFileMinimal(JSONObject json) { + fileName = json.getString("ProjectFileName"); + gameVersion = json.getString("GameVesion"); + fileType = json.getString("FileType"); + fileId = json.getInt("ProjectFileID"); + } + + public String getFileName() { + return fileName; + } + + public String getGameVersion() { + return gameVersion; + } + + public String getFileType() { + return fileType; + } + + public int getFileId() { + return fileId; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/model/ProjectFilesList.java b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFilesList.java new file mode 100644 index 0000000..3577728 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/model/ProjectFilesList.java @@ -0,0 +1,31 @@ +package com.github.franckyi.cmpdl.model; + +import org.json.JSONArray; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ProjectFilesList extends AbstractList { + + private final List list; + + public ProjectFilesList(JSONArray json) { + list = new ArrayList<>(json.length()); + for (int i = 0; i < json.length(); i++) { + list.add(new ProjectFile(json.getJSONObject(i))); + } + Collections.sort(list); + } + + @Override + public ProjectFile get(int index) { + return list.get(index); + } + + @Override + public int size() { + return list.size(); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/TaskBase.java b/src/main/java/com/github/franckyi/cmpdl/task/TaskBase.java new file mode 100644 index 0000000..a66c101 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/TaskBase.java @@ -0,0 +1,24 @@ +package com.github.franckyi.cmpdl.task; + +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.scene.control.Alert; + +import java.util.Optional; + +public abstract class TaskBase extends Task> { + + @Override + protected Optional call() { + try { + return Optional.ofNullable(call0()); + } catch (Throwable t) { + t.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, t.getMessage()).show()); + return Optional.empty(); + } + } + + protected abstract V call0() throws Throwable; + +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFileTask.java b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFileTask.java new file mode 100644 index 0000000..3b67eca --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFileTask.java @@ -0,0 +1,21 @@ +package com.github.franckyi.cmpdl.task.cursemeta; + +import com.github.franckyi.cmpdl.CurseMetaAPI; +import com.github.franckyi.cmpdl.model.ProjectFile; +import com.github.franckyi.cmpdl.task.TaskBase; + +public class GetProjectFileTask extends TaskBase { + + private final int projectId, fileId; + + public GetProjectFileTask(int projectId, int fileId) { + this.projectId = projectId; + this.fileId = fileId; + } + + @Override + protected ProjectFile call0() { + updateTitle(String.format("Getting project file %d:%d", projectId, fileId)); + return CurseMetaAPI.getProjectFile(projectId, fileId); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFilesTask.java b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFilesTask.java new file mode 100644 index 0000000..a74b31a --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectFilesTask.java @@ -0,0 +1,20 @@ +package com.github.franckyi.cmpdl.task.cursemeta; + +import com.github.franckyi.cmpdl.CurseMetaAPI; +import com.github.franckyi.cmpdl.model.ProjectFilesList; +import com.github.franckyi.cmpdl.task.TaskBase; + +public class GetProjectFilesTask extends TaskBase { + + private final int projectId; + + public GetProjectFilesTask(int projectId) { + this.projectId = projectId; + } + + @Override + protected ProjectFilesList call0() { + updateTitle(String.format("Getting project files for project %d", projectId)); + return CurseMetaAPI.getProjectFiles(projectId); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectIdTask.java b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectIdTask.java new file mode 100644 index 0000000..4fde0fc --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectIdTask.java @@ -0,0 +1,45 @@ +package com.github.franckyi.cmpdl.task.cursemeta; + +import com.github.franckyi.cmpdl.task.TaskBase; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +public class GetProjectIdTask extends TaskBase { + + private final String projectUrl; + + public GetProjectIdTask(String projectUrl) { + this.projectUrl = projectUrl; + } + + @Override + protected Integer call0() { + updateTitle(String.format("Getting project ID for %s", projectUrl)); + try { + URL url = new URL(projectUrl); + Document doc = Jsoup.parse(url, 10000); + if (url.getHost().equals("www.curseforge.com")) { + return new JSONObject(doc.select("a.button--twitch").get(0).attr("data-action-value")).getInt("ProjectID"); + } else { + return Integer.parseInt(doc.select("ul.cf-details.project-details").get(0).child(0).child(1).html()); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Malformed URL", ButtonType.OK).show()); + return null; + } catch (IOException e) { + e.printStackTrace(); + Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Connection error", ButtonType.OK).show()); + return null; + } + } + +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectTask.java b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectTask.java new file mode 100644 index 0000000..5140019 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/cursemeta/GetProjectTask.java @@ -0,0 +1,20 @@ +package com.github.franckyi.cmpdl.task.cursemeta; + +import com.github.franckyi.cmpdl.CurseMetaAPI; +import com.github.franckyi.cmpdl.model.Project; +import com.github.franckyi.cmpdl.task.TaskBase; + +public class GetProjectTask extends TaskBase { + + private final int projectId; + + public GetProjectTask(int projectId) { + this.projectId = projectId; + } + + @Override + protected Project call0() { + updateTitle(String.format("Getting project data for project %d", projectId)); + return CurseMetaAPI.getProject(projectId); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/mpimport/CopyOverridesTask.java b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/CopyOverridesTask.java new file mode 100644 index 0000000..7587bdd --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/CopyOverridesTask.java @@ -0,0 +1,61 @@ +package com.github.franckyi.cmpdl.task.mpimport; + +import com.github.franckyi.cmpdl.task.TaskBase; + +import java.io.File; +import java.io.IOException; +import java.nio.file.CopyOption; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +public class CopyOverridesTask extends TaskBase { + + private final File srcFolder, dstFolder; + + public CopyOverridesTask(File srcFolder, File dstFolder) { + this.srcFolder = srcFolder; + this.dstFolder = dstFolder; + } + + + @Override + protected Void call0() throws IOException { + updateTitle("Copying overrides"); + copyFolder(srcFolder, dstFolder, StandardCopyOption.REPLACE_EXISTING); + return null; + } + + public static void copyFileOrFolder(File source, File dest, CopyOption... options) throws IOException { + if (source.isDirectory()) + copyFolder(source, dest, options); + else { + ensureParentFolder(dest); + copyFile(source, dest, options); + } + } + + private static void copyFolder(File source, File dest, CopyOption... options) throws IOException { + if (!dest.exists()) + dest.mkdirs(); + File[] contents = source.listFiles(); + if (contents != null) { + for (File f : contents) { + File newFile = new File(dest.getAbsolutePath() + File.separator + f.getName()); + if (f.isDirectory()) + copyFolder(f, newFile, options); + else + copyFile(f, newFile, options); + } + } + } + + private static void copyFile(File source, File dest, CopyOption... options) throws IOException { + Files.copy(source.toPath(), dest.toPath(), options); + } + + private static void ensureParentFolder(File file) { + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) + parent.mkdirs(); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadFileTask.java b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadFileTask.java new file mode 100644 index 0000000..bfd82f2 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadFileTask.java @@ -0,0 +1,46 @@ +package com.github.franckyi.cmpdl.task.mpimport; + +import com.github.franckyi.cmpdl.task.TaskBase; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +public class DownloadFileTask extends TaskBase { + + private final String src; + private final File dst; + + public DownloadFileTask(String src, File dst) { + this.src = src; + this.dst = dst; + } + + @Override + protected Void call0() throws IOException, URISyntaxException { + updateTitle(String.format("Downloading %s", dst.getName())); + URL url = new URL(src); + URI uri = new URI(url.getProtocol(), url.getHost(), url.getFile(), null); + if (!dst.exists()) dst.createNewFile(); + HttpURLConnection connection = (HttpURLConnection) new URL(uri.toASCIIString()).openConnection(); + int size = connection.getContentLength(); + BufferedInputStream bis = new BufferedInputStream(connection.getInputStream()); + FileOutputStream fis = new FileOutputStream(dst); + 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, size); + } + fis.close(); + bis.close(); + return null; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadModsTask.java b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadModsTask.java new file mode 100644 index 0000000..33ce73b --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadModsTask.java @@ -0,0 +1,58 @@ +package com.github.franckyi.cmpdl.task.mpimport; + +import com.github.franckyi.cmpdl.CMPDL; +import com.github.franckyi.cmpdl.CurseMetaAPI; +import com.github.franckyi.cmpdl.model.ModpackManifest; +import com.github.franckyi.cmpdl.model.ProjectFile; +import com.github.franckyi.cmpdl.task.TaskBase; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.concurrent.Task; + +import java.io.File; +import java.util.List; + +public class DownloadModsTask extends TaskBase { + + private final File modsFolder; + private final List mods; + + private ObjectProperty> task = new SimpleObjectProperty<>(); + + public DownloadModsTask(File modsFolder, List mods) { + this.modsFolder = modsFolder; + this.mods = mods; + } + + @Override + protected Void call0() { + updateProgress(0, 1); + for (int i = 0; i < mods.size(); i++) { + if (isCancelled()) return null; + updateTitle(String.format("Downloading mods (%d/%d)", i + 1, mods.size())); + ModpackManifest.ModpackManifestMod mod = mods.get(i); + CMPDL.progressPane.getController().log("Resolving file %d:%d", mod.getProjectId(), mod.getFileId()); + ProjectFile file = CurseMetaAPI.getProjectFile(mod.getProjectId(), mod.getFileId()); + if (file != null) { + DownloadFileTask task = new DownloadFileTask(file.getDownloadUrl(), new File(modsFolder, file.getFileNameOnDisk())); + setTask(task); + CMPDL.progressPane.getController().log("Downloading file %s", file.getFileName().replaceAll("%", "")); + task.run(); + } else { + CMPDL.progressPane.getController().log("Unknown file %d:%d - skipping", mod.getProjectId(), mod.getFileId()); + } + updateProgress(i, mods.size()); + } + return null; + } + + private void setTask(Task task) { + Platform.runLater(() -> this.task.setValue(task)); + } + + public ReadOnlyObjectProperty> taskProperty() { + return task; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/mpimport/ReadManifestTask.java b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/ReadManifestTask.java new file mode 100644 index 0000000..e198451 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/ReadManifestTask.java @@ -0,0 +1,24 @@ +package com.github.franckyi.cmpdl.task.mpimport; + +import com.github.franckyi.cmpdl.model.ModpackManifest; +import com.github.franckyi.cmpdl.task.TaskBase; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +public class ReadManifestTask extends TaskBase { + + private final File manifestFile; + + public ReadManifestTask(File manifestFile) { + this.manifestFile = manifestFile; + } + + @Override + protected ModpackManifest call0() throws IOException { + return new ModpackManifest(new JSONObject(new String(Files.readAllBytes(manifestFile.toPath()), StandardCharsets.UTF_8))); + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/task/mpimport/UnzipFileTask.java b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/UnzipFileTask.java new file mode 100644 index 0000000..b9ccac9 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/task/mpimport/UnzipFileTask.java @@ -0,0 +1,54 @@ +package com.github.franckyi.cmpdl.task.mpimport; + +import com.github.franckyi.cmpdl.task.TaskBase; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class UnzipFileTask extends TaskBase { + + private final File src, dst; + + public UnzipFileTask(File src, File dst) { + this.src = src; + this.dst = dst; + } + + @Override + protected Void call0() throws Throwable { + updateTitle(String.format("Unzipping %s", src.getName())); + FileInputStream is = new FileInputStream(src.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(dst.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 = src.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; + } +} diff --git a/src/main/java/com/github/franckyi/cmpdl/view/ProjectFileMinimalView.java b/src/main/java/com/github/franckyi/cmpdl/view/ProjectFileMinimalView.java new file mode 100644 index 0000000..a5f9c05 --- /dev/null +++ b/src/main/java/com/github/franckyi/cmpdl/view/ProjectFileMinimalView.java @@ -0,0 +1,37 @@ +package com.github.franckyi.cmpdl.view; + +import com.github.franckyi.cmpdl.model.IProjectFile; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +public class ProjectFileMinimalView extends ListCell { + + private final VBox root = new VBox(5); + private final Label fileName = new Label(); + private final Label gameVersion = new Label(); + private final Label fileType = new Label(); + + public ProjectFileMinimalView() { + fileName.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, 16)); + fileType.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize())); + root.getChildren().addAll(fileName, gameVersion, fileType); + } + + @Override + protected void updateItem(IProjectFile item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setGraphic(null); + } else { + fileName.setText(item.getFileName()); + gameVersion.setText("for MC " + item.getGameVersion()); + fileType.setText(item.getFileType()); + fileType.setTextFill(item.getColor()); + setGraphic(root); + } + } + +} diff --git a/src/main/resources/DestinationPane.fxml b/src/main/resources/DestinationPane.fxml index f26e62c..c4c3fbf 100644 --- a/src/main/resources/DestinationPane.fxml +++ b/src/main/resources/DestinationPane.fxml @@ -1,9 +1,81 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +