├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── screenshots ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png └── screenshot4.png └── src └── main ├── java └── com │ └── github │ └── franckyi │ └── cmpdl │ ├── CMPDL.java │ ├── api │ ├── CMPDLConverterFactory.java │ ├── IBean.java │ ├── IEnum.java │ ├── TwitchAppAPI.java │ └── response │ │ ├── Addon.java │ │ ├── AddonFile.java │ │ ├── Attachment.java │ │ ├── Author.java │ │ ├── Category.java │ │ ├── CategorySection.java │ │ └── ReleaseType.java │ ├── controller │ ├── CleanTask.java │ ├── DestinationPaneController.java │ ├── FilePaneController.java │ ├── IContentController.java │ ├── MainWindowController.java │ ├── ModpackPaneController.java │ └── ProgressPaneController.java │ ├── core │ ├── ContentControllerView.java │ └── ControllerView.java │ ├── model │ └── ModpackManifest.java │ ├── task │ ├── TaskBase.java │ ├── api │ │ ├── CallTask.java │ │ └── GetProjectIdTask.java │ └── mpimport │ │ ├── CopyOverridesTask.java │ │ ├── DownloadFileTask.java │ │ ├── DownloadModsTask.java │ │ ├── ReadManifestTask.java │ │ └── UnzipFileTask.java │ └── view │ └── AddonFileMinimalView.java └── resources └── fxml ├── DestinationPane.fxml ├── FilePane.fxml ├── MainWindow.fxml ├── ModpackPane.fxml └── ProgressPane.fxml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # Log file 7 | *.log 8 | 9 | # BlueJ files 10 | *.ctxt 11 | 12 | # Mobile Tools for Java (J2ME) 13 | .mtj.tmp/ 14 | 15 | # Package Files # 16 | *.jar 17 | *.war 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | .idea/ 27 | target/* 28 | *.iml 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Franckyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMPDL (Curse Modpack Downloader) 2 | ### A lightweight alternative to Twitch App for downloading Minecraft modpacks. 3 | 4 | --- 5 | ## [Downloads](https://github.com/Franckyi/CMPDL/releases) 6 | CMPDL is a Java application created by Franckyi, based on [Vazkii's project](https://github.com/Vazkii/CMPDL). It allows you to download your favorite modpacks from websites like [CurseForge](https://minecraft.curseforge.com/modpacks) or [Feed the Beast](https://www.feed-the-beast.com/modpacks). For users who don't like the Twitch App and still want to enjoy Minecraft modpacks, this app is made for you ! 7 | 8 | This project is under the [MIT License](LICENSE). 9 | 10 | ### As this project is based on the JavaFX framework, Linux and MacOS system users must install OpenJFX or Oracle's JRE to start the application. 11 | 12 | What changed between Vazkii's version and my version ? 13 | 14 | - A revamped user interface 15 | - Support for [www.curseforge.com](http://www.curseforge.com) website 16 | - The user can choose the destination path 17 | 18 | ## How to use ? 19 | 20 | - Follow the steps from step 1 to step 3. 21 | - Press "Start" and wait until it finishes. 22 | - Forge won't be installed. The recommended Forge version to install will be shown in the log. You can also install a newer version, but if it breaks, go back to the recommended version. 23 | 24 | ## Screenshots 25 | ![Screenshot 1](screenshots/screenshot1.png) 26 | ![Screenshot 2](screenshots/screenshot2.png) 27 | ![Screenshot 3](screenshots/screenshot3.png) 28 | ![Screenshot 4](screenshots/screenshot4.png) 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.franckyi 8 | cmpdl 9 | 2.3.0 10 | 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | org.json 17 | json 18 | 20180813 19 | 20 | 21 | com.squareup.retrofit2 22 | retrofit 23 | 2.6.0 24 | 25 | 26 | org.jsoup 27 | jsoup 28 | 1.12.1 29 | 30 | 31 | 32 | 33 | 34 | maven-assembly-plugin 35 | 36 | 37 | 38 | com.github.franckyi.cmpdl.CMPDL 39 | 40 | 41 | 42 | jar-with-dependencies 43 | 44 | 45 | 46 | 47 | make-assembly 48 | package 49 | 50 | single 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyecodes/CMPDL/d7e2adaa1c01648a63f1235ae52c1fd7f16f94ba/screenshots/screenshot1.png -------------------------------------------------------------------------------- /screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyecodes/CMPDL/d7e2adaa1c01648a63f1235ae52c1fd7f16f94ba/screenshots/screenshot2.png -------------------------------------------------------------------------------- /screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyecodes/CMPDL/d7e2adaa1c01648a63f1235ae52c1fd7f16f94ba/screenshots/screenshot3.png -------------------------------------------------------------------------------- /screenshots/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyecodes/CMPDL/d7e2adaa1c01648a63f1235ae52c1fd7f16f94ba/screenshots/screenshot4.png -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/CMPDL.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl; 2 | 3 | import com.github.franckyi.cmpdl.api.CMPDLConverterFactory; 4 | import com.github.franckyi.cmpdl.api.TwitchAppAPI; 5 | import com.github.franckyi.cmpdl.controller.*; 6 | import com.github.franckyi.cmpdl.core.ContentControllerView; 7 | import com.github.franckyi.cmpdl.core.ControllerView; 8 | import javafx.application.Application; 9 | import javafx.application.Platform; 10 | import javafx.scene.Scene; 11 | import javafx.scene.control.Alert; 12 | import javafx.scene.control.ButtonType; 13 | import javafx.stage.Stage; 14 | import retrofit2.Retrofit; 15 | 16 | import java.awt.*; 17 | import java.io.IOException; 18 | import java.net.URI; 19 | import java.net.URISyntaxException; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | 23 | public class CMPDL extends Application { 24 | 25 | public static final String NAME = "CMPDL"; 26 | public static final String VERSION = "2.3.0"; 27 | public static final String AUTHOR = "Franckyi"; 28 | public static final String TITLE = String.format("%s v%s by %s", NAME, VERSION, AUTHOR); 29 | 30 | public static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); 31 | 32 | public static Stage stage; 33 | public static TwitchAppAPI api; 34 | 35 | public static ControllerView mainWindow; 36 | public static ContentControllerView modpackPane; 37 | public static ContentControllerView filePane; 38 | public static ContentControllerView destinationPane; 39 | public static ContentControllerView progressPane; 40 | 41 | public static ContentControllerView currentContent; 42 | 43 | public static void main(String[] args) { 44 | api = new Retrofit.Builder() 45 | .baseUrl("https://addons-ecs.forgesvc.net/api/v2/") 46 | .addConverterFactory(CMPDLConverterFactory.create()) 47 | .build() 48 | .create(TwitchAppAPI.class); 49 | launch(args); 50 | } 51 | 52 | @Override 53 | public void start(Stage primaryStage) throws Exception { 54 | stage = primaryStage; 55 | modpackPane = new ContentControllerView<>("fxml/ModpackPane.fxml"); 56 | filePane = new ContentControllerView<>("fxml/FilePane.fxml"); 57 | destinationPane = new ContentControllerView<>("fxml/DestinationPane.fxml"); 58 | progressPane = new ContentControllerView<>("fxml/ProgressPane.fxml"); 59 | mainWindow = new ControllerView<>("fxml/MainWindow.fxml"); 60 | stage.setScene(new Scene(mainWindow.getView())); 61 | stage.setTitle(TITLE); 62 | stage.setOnCloseRequest(e -> currentContent.getController().handleClose()); 63 | stage.show(); 64 | } 65 | 66 | @Override 67 | public void stop() { 68 | EXECUTOR_SERVICE.shutdown(); 69 | } 70 | 71 | public static TwitchAppAPI getAPI() { 72 | return api; 73 | } 74 | 75 | public static void openBrowser(String url) { 76 | if (Desktop.isDesktopSupported()) { 77 | EXECUTOR_SERVICE.execute(() -> { 78 | try { 79 | Desktop.getDesktop().browse(new URI(url)); 80 | } catch (IOException | URISyntaxException e) { 81 | Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Can't open URL", ButtonType.OK).show()); 82 | e.printStackTrace(); 83 | } 84 | }); 85 | } else { 86 | new Alert(Alert.AlertType.ERROR, "Desktop not supported", ButtonType.OK).show(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/CMPDLConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api; 2 | 3 | import okhttp3.MediaType; 4 | import okhttp3.RequestBody; 5 | import okhttp3.ResponseBody; 6 | import okio.BufferedSink; 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | import retrofit2.Converter; 10 | import retrofit2.Retrofit; 11 | 12 | import java.io.IOException; 13 | import java.lang.annotation.Annotation; 14 | import java.lang.reflect.ParameterizedType; 15 | import java.lang.reflect.Type; 16 | import java.time.Instant; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class CMPDLConverterFactory extends Converter.Factory { 21 | 22 | private CMPDLConverterFactory() { 23 | } 24 | 25 | public static CMPDLConverterFactory create() { 26 | return new CMPDLConverterFactory(); 27 | } 28 | 29 | @Override 30 | public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { 31 | return value -> { 32 | try { 33 | if (type instanceof Class) { 34 | Class clazz = (Class) type; 35 | if (clazz == Instant.class) { 36 | return Instant.parse(value.string()); 37 | } else if (IBean.class.isAssignableFrom(clazz)) { 38 | return ((IBean) clazz.newInstance()).fromJson(new JSONObject(value.string())); 39 | } 40 | } else if (type instanceof ParameterizedType) { 41 | ParameterizedType type0 = (ParameterizedType) type; 42 | if (List.class.isAssignableFrom((Class) type0.getRawType())) { 43 | return toList(type0, new JSONArray(value.string())); 44 | } 45 | } 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | return null; 50 | }; 51 | } 52 | 53 | @Override 54 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 55 | return value -> { 56 | try { 57 | if (value instanceof List) { 58 | return new JSONBody(fromList(((ParameterizedType) type), (List) value)); 59 | } else if (value instanceof IBean) { 60 | return new JSONBody(((IBean) value).toJson()); 61 | } 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | return null; 66 | }; 67 | } 68 | 69 | public static List toList(ParameterizedType type, JSONArray array) throws Exception { 70 | Class type0 = (Class) getParameterUpperBound(0, type); 71 | List list = new ArrayList<>(); 72 | if (IBean.class.isAssignableFrom(type0)) { 73 | for (int i = 0; i < array.length(); i++) { 74 | list.add(((IBean) type0.newInstance()).fromJson(array.getJSONObject(i))); 75 | } 76 | } else { 77 | list.addAll(array.toList()); 78 | } 79 | return list; 80 | } 81 | 82 | public static JSONArray fromList(List list) throws Exception { 83 | Type[] types = CMPDLConverterFactory.class.getDeclaredMethod("fromList", List.class).getGenericParameterTypes(); 84 | ParameterizedType type = (ParameterizedType) types[0]; 85 | return fromList(type, list); 86 | } 87 | 88 | public static JSONArray fromList(ParameterizedType type, List list) { 89 | Class clazz = (Class) type.getActualTypeArguments()[0]; 90 | return fromList(clazz, list); 91 | } 92 | 93 | public static JSONArray fromList(Class clazz, List list) { 94 | JSONArray array = new JSONArray(); 95 | if (IBean.class.isAssignableFrom(clazz)) { 96 | list.stream() 97 | .map(IBean.class::cast) 98 | .map(IBean::toJson) 99 | .forEach(array::put); 100 | } else { 101 | array.put(list); 102 | } 103 | return array; 104 | } 105 | 106 | private class JSONBody extends RequestBody { 107 | private final Object json; 108 | 109 | public JSONBody(JSONArray json) { 110 | this.json = json; 111 | } 112 | 113 | public JSONBody(JSONObject json) { 114 | this.json = json; 115 | } 116 | 117 | @Override 118 | public MediaType contentType() { 119 | return MediaType.get("application/json"); 120 | } 121 | 122 | @Override 123 | public void writeTo(BufferedSink sink) throws IOException { 124 | sink.writeUtf8(json.toString()); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/IBean.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.ParameterizedType; 8 | import java.time.Instant; 9 | import java.util.List; 10 | 11 | public interface IBean { 12 | 13 | default IBean fromJson(JSONObject json) throws Exception { 14 | for (Field field : this.getClass().getDeclaredFields()) { 15 | if (json.has(field.getName())) { 16 | field.setAccessible(true); 17 | Object val = json.get(field.getName()); 18 | if (val == JSONObject.NULL) { 19 | if (field.getType().isPrimitive()) { 20 | if (field.getType().equals(boolean.class)) { 21 | val = false; 22 | } else if (field.getType().equals(byte.class)) { 23 | val = (byte) 0; 24 | } else if (field.getType().equals(short.class)) { 25 | val = (short) 0; 26 | } else if (field.getType().equals(int.class)) { 27 | val = 0; 28 | } else if (field.getType().equals(long.class)) { 29 | val = 0L; 30 | } else if (field.getType().equals(float.class)) { 31 | val = 0f; 32 | } else if (field.getType().equals(double.class)) { 33 | val = 0.0; 34 | } 35 | field.set(this, val); 36 | } else { 37 | field.set(this, null); 38 | } 39 | } else if (val instanceof JSONObject && IBean.class.isAssignableFrom(field.getType())) { 40 | field.set(this, ((IBean) field.getType().newInstance()).fromJson(((JSONObject) val))); 41 | } else if (val instanceof JSONArray && List.class.isAssignableFrom(field.getType())) { 42 | ParameterizedType type = (ParameterizedType) field.getGenericType(); 43 | JSONArray array = (JSONArray) val; 44 | field.set(this, CMPDLConverterFactory.toList(type, array)); 45 | } else if (val instanceof Integer && Enum.class.isAssignableFrom(field.getType())) { 46 | field.set(this, IEnum.fromJson(field.getType(), (int) val)); 47 | } else if (val instanceof String && Instant.class.isAssignableFrom(field.getType())) { 48 | field.set(this, Instant.parse((String) val)); 49 | } else { 50 | field.set(this, val); 51 | } 52 | } 53 | } 54 | return this; 55 | } 56 | 57 | default JSONObject toJson() { 58 | JSONObject json = new JSONObject(); 59 | for (Field field : this.getClass().getDeclaredFields()) { 60 | try { 61 | Object val = field.get(this); 62 | if (val instanceof IBean) { 63 | json.put(field.getName(), ((IBean) val).toJson()); 64 | } else if (val instanceof List) { 65 | List list = (List) val; 66 | json.put(field.getName(), CMPDLConverterFactory.fromList(list)); 67 | } else if (val instanceof IEnum) { 68 | json.put(field.getName(), ((IEnum) val).toJson()); 69 | } 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | return json; 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/IEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | public interface IEnum { 6 | 7 | int toJson(); 8 | 9 | @SuppressWarnings("unchecked") 10 | static & IEnum> T fromJson(Class clazz, int i) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 11 | T[] values = (T[]) clazz.getDeclaredMethod("values").invoke(null); 12 | for (T value : values) { 13 | if (value.toJson() == i) { 14 | return value; 15 | } 16 | } 17 | return null; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/TwitchAppAPI.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api; 2 | 3 | import com.github.franckyi.cmpdl.api.response.Addon; 4 | import com.github.franckyi.cmpdl.api.response.AddonFile; 5 | import retrofit2.Call; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | 9 | import java.util.List; 10 | 11 | public interface TwitchAppAPI { 12 | 13 | @GET("addon/{addonId}") 14 | Call getAddon(@Path("addonId") int addonId); 15 | 16 | @GET("addon/{addonId}/file/{fileId}") 17 | Call getFile(@Path("addonId") int addonId, @Path("fileId") int fileId); 18 | 19 | @GET("addon/{addonId}/files") 20 | Call> getAddonFiles(@Path("addonId") int addonId); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/Addon.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | import java.util.List; 6 | 7 | public class Addon implements IBean { 8 | 9 | private int id; 10 | private String name; 11 | private List authors; 12 | private List attachments; 13 | private String websiteUrl; 14 | private String summary; 15 | private List latestFiles; 16 | private List categories; 17 | private int primaryCategoryId; 18 | private CategorySection categorySection; 19 | 20 | public int getId() { 21 | return id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public List getAuthors() { 29 | return authors; 30 | } 31 | 32 | public List getAttachments() { 33 | return attachments; 34 | } 35 | 36 | public String getWebsiteUrl() { 37 | return websiteUrl; 38 | } 39 | 40 | public String getSummary() { 41 | return summary; 42 | } 43 | 44 | public List getLatestFiles() { 45 | return latestFiles; 46 | } 47 | 48 | public List getCategories() { 49 | return categories; 50 | } 51 | 52 | public int getPrimaryCategoryId() { 53 | return primaryCategoryId; 54 | } 55 | 56 | public CategorySection getCategorySection() { 57 | return categorySection; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/AddonFile.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | import java.time.Instant; 6 | import java.util.List; 7 | 8 | public class AddonFile implements IBean { 9 | 10 | private int id; 11 | private String displayName; 12 | private String fileName; 13 | private Instant fileDate; 14 | private ReleaseType releaseType; 15 | private String downloadUrl; 16 | private List gameVersion; 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public String getDisplayName() { 23 | return displayName; 24 | } 25 | 26 | public String getFileName() { 27 | return fileName; 28 | } 29 | 30 | public Instant getFileDate() { 31 | return fileDate; 32 | } 33 | 34 | public ReleaseType getReleaseType() { 35 | return releaseType; 36 | } 37 | 38 | public String getDownloadUrl() { 39 | return downloadUrl; 40 | } 41 | 42 | public List getGameVersion() { 43 | return gameVersion; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | public class Attachment implements IBean { 6 | 7 | private boolean isDefault; 8 | private String thumbnailUrl; 9 | 10 | public boolean isDefault() { 11 | return isDefault; 12 | } 13 | 14 | public String getThumbnailUrl() { 15 | return thumbnailUrl; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/Author.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | public class Author implements IBean { 6 | 7 | private String name; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/Category.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | public class Category implements IBean { 6 | 7 | private int categoryId; 8 | private String name; 9 | private String avatarUrl; 10 | 11 | public int getCategoryId() { 12 | return categoryId; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public String getAvatarUrl() { 20 | return avatarUrl; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/CategorySection.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IBean; 4 | 5 | public class CategorySection implements IBean { 6 | 7 | private String name; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/api/response/ReleaseType.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.api.response; 2 | 3 | import com.github.franckyi.cmpdl.api.IEnum; 4 | import javafx.scene.paint.Color; 5 | 6 | public enum ReleaseType implements IEnum { 7 | RELEASE(1, 20, 184, 102), 8 | BETA(2, 14, 115, 216), 9 | ALPHA(3, 126, 121, 139); 10 | 11 | private final int id; 12 | private final Color color; 13 | 14 | ReleaseType(int id, int r, int g, int b) { 15 | this.id = id; 16 | this.color = Color.rgb(r, g, b); 17 | } 18 | 19 | @Override 20 | public int toJson() { 21 | return id; 22 | } 23 | 24 | public Color getColor() { 25 | return color; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/CleanTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.FileVisitResult; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.SimpleFileVisitor; 11 | import java.nio.file.attribute.BasicFileAttributes; 12 | 13 | public class CleanTask extends TaskBase { 14 | 15 | private final File temp; 16 | 17 | public CleanTask(File temp) { 18 | this.temp = temp; 19 | } 20 | 21 | @Override 22 | protected Void call0() throws Throwable { 23 | updateTitle("Cleaning"); 24 | Files.walkFileTree(temp.toPath(), new SimpleFileVisitor() { 25 | @Override 26 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 27 | Files.delete(file); 28 | return FileVisitResult.CONTINUE; 29 | } 30 | 31 | @Override 32 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 33 | Files.delete(dir); 34 | return FileVisitResult.CONTINUE; 35 | } 36 | }); 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/DestinationPaneController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.api.response.Addon; 5 | import com.github.franckyi.cmpdl.api.response.AddonFile; 6 | import com.github.franckyi.cmpdl.api.response.Attachment; 7 | import javafx.event.ActionEvent; 8 | import javafx.fxml.FXML; 9 | import javafx.fxml.Initializable; 10 | import javafx.geometry.Insets; 11 | import javafx.scene.control.Alert; 12 | import javafx.scene.control.ButtonType; 13 | import javafx.scene.control.Label; 14 | import javafx.scene.control.TextField; 15 | import javafx.scene.image.Image; 16 | import javafx.scene.image.ImageView; 17 | import javafx.scene.layout.Background; 18 | import javafx.scene.layout.BackgroundFill; 19 | import javafx.scene.layout.CornerRadii; 20 | import javafx.scene.paint.Color; 21 | import javafx.scene.text.Font; 22 | import javafx.scene.text.FontWeight; 23 | import javafx.stage.DirectoryChooser; 24 | 25 | import java.io.File; 26 | import java.net.URL; 27 | import java.util.ResourceBundle; 28 | 29 | public class DestinationPaneController implements Initializable, IContentController { 30 | 31 | private Addon addon; 32 | private AddonFile addonFile; 33 | 34 | @FXML 35 | private ImageView logoImageView; 36 | 37 | @FXML 38 | private Label titleLabel; 39 | 40 | @FXML 41 | private Label authorLabel; 42 | 43 | @FXML 44 | private Label summaryLabel; 45 | 46 | @FXML 47 | private ImageView categoryImageView; 48 | 49 | @FXML 50 | private Label categoryLabel; 51 | 52 | @FXML 53 | private Label fileNameLabel; 54 | 55 | @FXML 56 | private Label mcVersionLabel; 57 | 58 | @FXML 59 | private Label releaseTypeLabel; 60 | 61 | @FXML 62 | private TextField destinationField; 63 | 64 | @FXML 65 | void actionChooseDestination(ActionEvent event) { 66 | DirectoryChooser dc = new DirectoryChooser(); 67 | dc.setTitle("Choose the destination folder :"); 68 | String currentChosenDirectory = destinationField.getText(); 69 | if (currentChosenDirectory != null && ! "".equals(currentChosenDirectory)) { 70 | dc.setInitialDirectory(new File(currentChosenDirectory)); 71 | } 72 | File dst = dc.showDialog(CMPDL.stage); 73 | if (dst != null) { 74 | destinationField.setText(dst.getAbsolutePath()); 75 | } 76 | } 77 | 78 | @FXML 79 | void actionViewInBrowser(ActionEvent event) { 80 | CMPDL.openBrowser(addon.getWebsiteUrl()); 81 | } 82 | 83 | @Override 84 | public void initialize(URL location, ResourceBundle resources) { 85 | destinationField.textProperty().addListener((o, oldValue, newValue) -> CMPDL.mainWindow.getController().getStartButton().setDisable(newValue.isEmpty())); 86 | releaseTypeLabel.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize())); 87 | } 88 | 89 | @Override 90 | public void handleNext() { 91 | 92 | } 93 | 94 | @Override 95 | public void handlePrevious() { 96 | CMPDL.mainWindow.getController().setContent(CMPDL.filePane); 97 | CMPDL.mainWindow.getController().getStartButton().setDisable(true); 98 | CMPDL.mainWindow.getController().getNextButton().setDisable(false); 99 | } 100 | 101 | @Override 102 | public void handleStart() { 103 | File dst = new File(destinationField.getText()); 104 | if (dst.isDirectory()) { 105 | if (!dst.exists()) { 106 | dst.mkdirs(); 107 | } 108 | if (!dst.canWrite()) { 109 | new Alert(Alert.AlertType.ERROR, "Permission denied. Please choose another destination folder.", ButtonType.OK).show(); 110 | } else { 111 | CMPDL.progressPane.getController().setData(addonFile, dst); 112 | CMPDL.mainWindow.getController().setContent(CMPDL.progressPane); 113 | CMPDL.mainWindow.getController().getStartButton().setDisable(true); 114 | CMPDL.mainWindow.getController().getPreviousButton().setDisable(true); 115 | CMPDL.progressPane.getController().start(); 116 | } 117 | } else { 118 | new Alert(Alert.AlertType.ERROR, "The destination must be a folder.", ButtonType.OK).show(); 119 | } 120 | } 121 | 122 | public void setAddonAndFile(Addon addon, AddonFile file) { 123 | this.addon = addon; 124 | this.addonFile = file; 125 | addon.getAttachments().stream() 126 | .filter(Attachment::isDefault) 127 | .findFirst() 128 | .ifPresent(a -> logoImageView.setImage(new Image(a.getThumbnailUrl()))); 129 | titleLabel.setText(addon.getName()); 130 | authorLabel.setText("by " + addon.getAuthors().get(0).getName()); 131 | summaryLabel.setText(addon.getSummary()); 132 | addon.getCategories().stream() 133 | .filter(category -> category.getCategoryId() == addon.getPrimaryCategoryId()) 134 | .findFirst() 135 | .ifPresent(c -> { 136 | categoryImageView.setImage(new Image(c.getAvatarUrl())); 137 | categoryLabel.setText(c.getName()); 138 | }); 139 | fileNameLabel.setText(file.getDisplayName()); 140 | mcVersionLabel.setText(String.join(", ", file.getGameVersion())); 141 | releaseTypeLabel.setText(file.getReleaseType().toString()); 142 | releaseTypeLabel.setBackground(new Background(new BackgroundFill(file.getReleaseType().getColor(), new CornerRadii(5), new Insets(-2, -5, -2, -5)))); 143 | releaseTypeLabel.setTextFill(Color.WHITE); 144 | 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/FilePaneController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.api.response.Addon; 5 | import com.github.franckyi.cmpdl.api.response.AddonFile; 6 | import com.github.franckyi.cmpdl.api.response.Attachment; 7 | import com.github.franckyi.cmpdl.task.api.CallTask; 8 | import com.github.franckyi.cmpdl.view.AddonFileMinimalView; 9 | import javafx.application.Platform; 10 | import javafx.collections.ListChangeListener; 11 | import javafx.event.ActionEvent; 12 | import javafx.fxml.FXML; 13 | import javafx.fxml.Initializable; 14 | import javafx.scene.control.*; 15 | import javafx.scene.image.Image; 16 | import javafx.scene.image.ImageView; 17 | import javafx.scene.layout.VBox; 18 | 19 | import java.net.URL; 20 | import java.util.Comparator; 21 | import java.util.List; 22 | import java.util.ResourceBundle; 23 | 24 | public class FilePaneController implements Initializable, IContentController { 25 | 26 | private Addon addon; 27 | private List files = null; 28 | 29 | @FXML 30 | private ImageView logoImageView; 31 | 32 | @FXML 33 | private Label titleLabel; 34 | 35 | @FXML 36 | private Label authorLabel; 37 | 38 | @FXML 39 | private Label summaryLabel; 40 | 41 | @FXML 42 | private ImageView categoryImageView; 43 | 44 | @FXML 45 | private Label categoryLabel; 46 | 47 | @FXML 48 | private ToggleGroup file; 49 | 50 | @FXML 51 | private RadioButton directButton; 52 | 53 | @FXML 54 | private VBox directPane; 55 | 56 | @FXML 57 | private ListView filesListView; 58 | 59 | @FXML 60 | private Button changeViewButton; 61 | 62 | @FXML 63 | private RadioButton idButton; 64 | 65 | @FXML 66 | private TextField idField; 67 | 68 | @Override 69 | public void initialize(URL location, ResourceBundle resources) { 70 | directPane.disableProperty().bind(directButton.selectedProperty().not()); 71 | idField.disableProperty().bind(idButton.selectedProperty().not()); 72 | filesListView.setCellFactory(param -> new AddonFileMinimalView()); 73 | filesListView.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { 74 | if (CMPDL.currentContent == CMPDL.filePane && directButton.isSelected()) { 75 | CMPDL.mainWindow.getController().getNextButton().setDisable(c.getList().size() != 1); 76 | } 77 | }); 78 | idButton.selectedProperty().addListener((observable, oldValue, newValue) -> 79 | CMPDL.mainWindow.getController().getNextButton().setDisable(!newValue && filesListView.getSelectionModel().getSelectedItems().isEmpty())); 80 | } 81 | 82 | private void viewAllFiles() { 83 | if (files != null) { 84 | filesListView.getItems().setAll(files); 85 | changeViewButton.setText("View latest files..."); 86 | changeViewButton.setOnAction(e -> viewLatestFiles()); 87 | } 88 | } 89 | 90 | public void viewLatestFiles() { 91 | filesListView.getItems().setAll(addon.getLatestFiles()); 92 | changeViewButton.setText("View all files..."); 93 | changeViewButton.setOnAction(e -> viewAllFiles()); 94 | } 95 | 96 | @FXML 97 | void actionViewInBrowser(ActionEvent event) { 98 | CMPDL.openBrowser(addon.getWebsiteUrl()); 99 | } 100 | 101 | public void setAddon(Addon addon) { 102 | this.addon = addon; 103 | addon.getLatestFiles().sort(Comparator.comparing(AddonFile::getFileDate).reversed()); 104 | addon.getAttachments().stream() 105 | .filter(Attachment::isDefault) 106 | .findFirst() 107 | .ifPresent(a -> logoImageView.setImage(new Image(a.getThumbnailUrl()))); 108 | titleLabel.setText(addon.getName()); 109 | authorLabel.setText("by " + addon.getAuthors().get(0).getName()); 110 | summaryLabel.setText(addon.getSummary()); 111 | addon.getCategories().stream() 112 | .filter(category -> category.getCategoryId() == addon.getPrimaryCategoryId()) 113 | .findFirst() 114 | .ifPresent(c -> { 115 | categoryImageView.setImage(new Image(c.getAvatarUrl())); 116 | categoryLabel.setText(c.getName()); 117 | }); 118 | CMPDL.mainWindow.getController().getNextButton().setDisable(true); 119 | CMPDL.mainWindow.getController().getPreviousButton().setDisable(false); 120 | CallTask> task = new CallTask<>(String.format("Getting addon files for addon %d", addon.getId()), CMPDL.getAPI().getAddonFiles(addon.getId())); 121 | task.setOnSucceeded(e -> files = task.getValue().orElse(null)); 122 | if (files != null) { 123 | files.sort(Comparator.comparing(AddonFile::getFileDate).reversed()); 124 | } 125 | CMPDL.EXECUTOR_SERVICE.execute(task); 126 | } 127 | 128 | @Override 129 | public void handleNext() { 130 | Alert alert = new Alert(Alert.AlertType.INFORMATION, "Loading file data...", ButtonType.CLOSE); 131 | alert.show(); 132 | int fileId = file.getSelectedToggle() == directButton ? filesListView.getSelectionModel().getSelectedItem().getId() : Integer.parseInt(idField.getText()); 133 | CallTask task = new CallTask<>(String.format("Getting project file %d:%d", addon.getId(), fileId), CMPDL.getAPI().getFile(addon.getId(), fileId)); 134 | task.setOnSucceeded(e -> Platform.runLater(() -> task.getValue().ifPresent(file -> { 135 | CMPDL.destinationPane.getController().setAddonAndFile(addon, file); 136 | CMPDL.mainWindow.getController().setContent(CMPDL.destinationPane); 137 | CMPDL.mainWindow.getController().getNextButton().setDisable(true); 138 | alert.hide(); 139 | }))); 140 | CMPDL.EXECUTOR_SERVICE.execute(task); 141 | } 142 | 143 | @Override 144 | public void handlePrevious() { 145 | CMPDL.mainWindow.getController().getStartButton().disableProperty().bind(CMPDL.modpackPane.getController().getZipButton().selectedProperty().not()); 146 | CMPDL.mainWindow.getController().getNextButton().disableProperty().bind(CMPDL.modpackPane.getController().getZipButton().selectedProperty()); 147 | CMPDL.mainWindow.getController().setContent(CMPDL.modpackPane); 148 | CMPDL.mainWindow.getController().getNextButton().disableProperty().unbind(); 149 | CMPDL.mainWindow.getController().getNextButton().setDisable(false); 150 | CMPDL.mainWindow.getController().getPreviousButton().setDisable(true); 151 | } 152 | 153 | @Override 154 | public void handleStart() { 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/IContentController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import javafx.application.Platform; 4 | 5 | public interface IContentController { 6 | 7 | void handleNext(); 8 | 9 | void handlePrevious(); 10 | 11 | default void handleClose() { 12 | Platform.exit(); 13 | } 14 | 15 | void handleStart(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/MainWindowController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.core.ContentControllerView; 5 | import javafx.event.ActionEvent; 6 | import javafx.fxml.FXML; 7 | import javafx.fxml.Initializable; 8 | import javafx.scene.control.Button; 9 | import javafx.scene.input.MouseEvent; 10 | import javafx.scene.layout.AnchorPane; 11 | 12 | import java.net.URL; 13 | import java.util.ResourceBundle; 14 | 15 | public class MainWindowController implements Initializable { 16 | 17 | private boolean darkTheme = false; 18 | 19 | @FXML 20 | private AnchorPane contentPane; 21 | 22 | @FXML 23 | private Button closeButton; 24 | 25 | @FXML 26 | private Button previousButton; 27 | 28 | @FXML 29 | private Button nextButton; 30 | 31 | @FXML 32 | private Button startButton; 33 | 34 | @FXML 35 | void actionClose(ActionEvent event) { 36 | CMPDL.currentContent.getController().handleClose(); 37 | } 38 | 39 | @FXML 40 | void actionStart(ActionEvent event) { 41 | CMPDL.currentContent.getController().handleStart(); 42 | } 43 | 44 | @FXML 45 | void actionNext(ActionEvent event) { 46 | CMPDL.currentContent.getController().handleNext(); 47 | } 48 | 49 | @FXML 50 | void actionPrevious(ActionEvent event) { 51 | CMPDL.currentContent.getController().handlePrevious(); 52 | } 53 | 54 | @Override 55 | public void initialize(URL location, ResourceBundle resources) { 56 | setContent(CMPDL.modpackPane); 57 | startButton.disableProperty().bind(CMPDL.modpackPane.getController().getZipButton().selectedProperty().not()); 58 | nextButton.disableProperty().bind(CMPDL.modpackPane.getController().getZipButton().selectedProperty()); 59 | } 60 | 61 | public void setContent(ContentControllerView cv) { 62 | contentPane.getChildren().clear(); 63 | contentPane.getChildren().add(cv.getView()); 64 | CMPDL.currentContent = cv; 65 | CMPDL.stage.sizeToScene(); 66 | } 67 | 68 | public Button getPreviousButton() { 69 | return previousButton; 70 | } 71 | 72 | public Button getNextButton() { 73 | return nextButton; 74 | } 75 | 76 | public Button getStartButton() { 77 | return startButton; 78 | } 79 | 80 | public void switchTheme(MouseEvent mouseEvent) { 81 | if (darkTheme) { 82 | CMPDL.mainWindow.getView().setStyle("-fx-base:#ececec;"); 83 | } else { 84 | CMPDL.mainWindow.getView().setStyle("-fx-base:black;"); 85 | } 86 | darkTheme = !darkTheme; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/ModpackPaneController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.api.response.Addon; 5 | import com.github.franckyi.cmpdl.task.api.CallTask; 6 | import com.github.franckyi.cmpdl.task.api.GetProjectIdTask; 7 | import javafx.application.Platform; 8 | import javafx.event.ActionEvent; 9 | import javafx.fxml.FXML; 10 | import javafx.fxml.Initializable; 11 | import javafx.scene.control.*; 12 | import javafx.stage.DirectoryChooser; 13 | import javafx.stage.FileChooser; 14 | 15 | import java.io.File; 16 | import java.net.URL; 17 | import java.util.ResourceBundle; 18 | 19 | public class ModpackPaneController implements Initializable, IContentController { 20 | 21 | @FXML 22 | private TextField urlField; 23 | 24 | @FXML 25 | private TextField idField; 26 | 27 | @FXML 28 | private TextField zipField; 29 | 30 | @FXML 31 | private TextField destinationField; 32 | 33 | @FXML 34 | private RadioButton urlButton; 35 | 36 | @FXML 37 | private RadioButton idButton; 38 | 39 | @FXML 40 | private RadioButton zipButton; 41 | 42 | @FXML 43 | private Button chooseSourceButton; 44 | 45 | @FXML 46 | private Button chooseDestinationButton; 47 | 48 | @FXML 49 | private ToggleGroup modpack; 50 | 51 | public RadioButton getZipButton() { 52 | return zipButton; 53 | } 54 | 55 | @Override 56 | public void initialize(URL location, ResourceBundle resources) { 57 | urlField.disableProperty().bind(urlButton.selectedProperty().not()); 58 | idField.disableProperty().bind(idButton.selectedProperty().not()); 59 | zipField.disableProperty().bind(zipButton.selectedProperty().not()); 60 | destinationField.disableProperty().bind(zipButton.selectedProperty().not()); 61 | chooseSourceButton.disableProperty().bind(zipButton.selectedProperty().not()); 62 | chooseDestinationButton.disableProperty().bind(zipButton.selectedProperty().not()); 63 | } 64 | 65 | @Override 66 | public void handleNext() { 67 | if (modpack.getSelectedToggle() == urlButton) { 68 | Alert alert = new Alert(Alert.AlertType.INFORMATION, "Parsing project ID...", ButtonType.CLOSE); 69 | alert.show(); 70 | GetProjectIdTask task = new GetProjectIdTask(urlField.getText()); 71 | task.setOnSucceeded(event -> { 72 | alert.hide(); 73 | handleNext(task.getValue().orElse(-1)); 74 | }); 75 | CMPDL.EXECUTOR_SERVICE.execute(task); 76 | } else if (modpack.getSelectedToggle() == idButton) { 77 | int projectId = -1; 78 | try { 79 | projectId = Integer.parseInt(idField.getText()); 80 | } catch (NumberFormatException e) { 81 | e.printStackTrace(); 82 | new Alert(Alert.AlertType.ERROR, "The project ID must be an integer", ButtonType.OK).show(); 83 | } 84 | handleNext(projectId); 85 | } 86 | } 87 | 88 | @Override 89 | public void handlePrevious() { 90 | 91 | } 92 | 93 | @Override 94 | public void handleStart() { 95 | if (modpack.getSelectedToggle() == zipButton) { 96 | File zipFile = new File(zipField.getText()); 97 | File dstFolder = new File(destinationField.getText()); 98 | if (zipFile.exists()) { 99 | dstFolder.mkdirs(); 100 | CMPDL.mainWindow.getController().getStartButton().disableProperty().unbind(); 101 | CMPDL.mainWindow.getController().getNextButton().disableProperty().unbind(); 102 | CMPDL.mainWindow.getController().getStartButton().setDisable(true); 103 | CMPDL.progressPane.getController().setData(zipFile, dstFolder); 104 | CMPDL.progressPane.getController().unzipModpack(); 105 | CMPDL.mainWindow.getController().setContent(CMPDL.progressPane); 106 | } 107 | } 108 | } 109 | 110 | public void handleNext(int addonId) { 111 | Alert alert = new Alert(Alert.AlertType.INFORMATION, "Loading project data...", ButtonType.CLOSE); 112 | alert.show(); 113 | CallTask task = new CallTask<>(String.format("Getting project data for project %d", addonId), CMPDL.getAPI().getAddon(addonId)); 114 | task.setOnSucceeded(event -> Platform.runLater(() -> task.getValue().ifPresent(addon -> { 115 | if (addon.getCategorySection().getName().equals("Modpacks")) { 116 | CMPDL.mainWindow.getController().getStartButton().disableProperty().unbind(); 117 | CMPDL.mainWindow.getController().getNextButton().disableProperty().unbind(); 118 | CMPDL.filePane.getController().setAddon(addon); 119 | CMPDL.filePane.getController().viewLatestFiles(); 120 | CMPDL.mainWindow.getController().setContent(CMPDL.filePane); 121 | alert.hide(); 122 | } else { 123 | alert.hide(); 124 | new Alert(Alert.AlertType.ERROR, "The addon isn't a modpack !", ButtonType.OK).show(); 125 | } 126 | }))); 127 | CMPDL.EXECUTOR_SERVICE.execute(task); 128 | } 129 | 130 | @FXML 131 | void actionChooseSource(ActionEvent event) { 132 | FileChooser fc = new FileChooser(); 133 | fc.setTitle("Choose the modpack file :"); 134 | fc.getExtensionFilters().add(new FileChooser.ExtensionFilter("Modpack archive", "*.zip")); 135 | File src = fc.showOpenDialog(CMPDL.stage); 136 | if (src != null) { 137 | zipField.setText(src.getAbsolutePath()); 138 | } 139 | } 140 | 141 | @FXML 142 | void actionChooseDestination(ActionEvent event) { 143 | DirectoryChooser dc = new DirectoryChooser(); 144 | dc.setTitle("Choose the destination folder :"); 145 | File dst = dc.showDialog(CMPDL.stage); 146 | if (dst != null) { 147 | destinationField.setText(dst.getAbsolutePath()); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/controller/ProgressPaneController.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.controller; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.api.response.AddonFile; 5 | import com.github.franckyi.cmpdl.model.ModpackManifest; 6 | import com.github.franckyi.cmpdl.task.mpimport.*; 7 | import javafx.application.Platform; 8 | import javafx.concurrent.Task; 9 | import javafx.fxml.FXML; 10 | import javafx.fxml.Initializable; 11 | import javafx.scene.control.*; 12 | 13 | import java.io.File; 14 | import java.net.URL; 15 | import java.util.Optional; 16 | import java.util.ResourceBundle; 17 | 18 | public class ProgressPaneController implements Initializable, IContentController { 19 | 20 | private Task task1, task2; 21 | private boolean done = false; 22 | 23 | private AddonFile addonFile; 24 | private File destination, zipFile, unzipFolder, minecraft, modsFolder, temp, progressFile; 25 | private ModpackManifest manifest; 26 | 27 | @FXML 28 | private DialogPane root; 29 | 30 | @FXML 31 | private Label titleLabel; 32 | 33 | @FXML 34 | private Label subLabel1; 35 | 36 | @FXML 37 | private ProgressBar progressBar1; 38 | 39 | @FXML 40 | private ProgressIndicator progressIndicator1; 41 | 42 | @FXML 43 | private Label subLabel2; 44 | 45 | @FXML 46 | private ProgressBar progressBar2; 47 | 48 | @FXML 49 | private ProgressIndicator progressIndicator2; 50 | 51 | @FXML 52 | private TextArea console; 53 | 54 | @Override 55 | public void initialize(URL location, ResourceBundle resources) { 56 | root.expandedProperty().addListener((observable, oldValue, newValue) -> CMPDL.stage.sizeToScene()); 57 | } 58 | 59 | @Override 60 | public void handleNext() { 61 | 62 | } 63 | 64 | @Override 65 | public void handlePrevious() { 66 | 67 | } 68 | 69 | @Override 70 | public void handleStart() { 71 | 72 | } 73 | 74 | @Override 75 | public void handleClose() { 76 | if (!done) { 77 | Optional buttonType = new Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to cancel the modpack download ?", ButtonType.YES, ButtonType.NO).showAndWait(); 78 | if (buttonType.orElse(null) == ButtonType.YES) { 79 | if (task2 != null) task2.cancel(); 80 | if (task1 != null) task1.cancel(); 81 | Platform.exit(); 82 | } 83 | } else { 84 | Platform.exit(); 85 | } 86 | } 87 | 88 | public void setData(AddonFile addonFile, File destination) { 89 | this.addonFile = addonFile; 90 | this.destination = destination; 91 | minecraft = new File(destination, "minecraft"); 92 | minecraft.mkdirs(); 93 | modsFolder = new File(minecraft, "mods"); 94 | modsFolder.mkdirs(); 95 | temp = new File(destination, ".cmpdl_temp"); 96 | temp.mkdirs(); 97 | progressFile = new File(temp, ".progress"); 98 | zipFile = new File(temp, addonFile.getFileName()); 99 | unzipFolder = new File(temp, addonFile.getFileName().replace(".zip", "")); 100 | unzipFolder.mkdirs(); 101 | } 102 | 103 | public void setData(File zipFile, File dstFolder) { 104 | this.zipFile = zipFile; 105 | this.destination = dstFolder; 106 | minecraft = new File(destination, "minecraft"); 107 | minecraft.mkdirs(); 108 | modsFolder = new File(minecraft, "mods"); 109 | modsFolder.mkdirs(); 110 | temp = new File(destination, ".cmpdl_temp"); 111 | temp.mkdirs(); 112 | progressFile = new File(temp, ".progress"); 113 | unzipFolder = new File(temp, zipFile.getName().replace(".zip", "")); 114 | unzipFolder.mkdirs(); 115 | } 116 | 117 | private void setTask1(Task task) { 118 | task1 = task; 119 | bind(task, subLabel1, progressBar1, progressIndicator1); 120 | } 121 | 122 | private void setTask2(Task task) { 123 | task2 = task; 124 | bind(task, subLabel2, progressBar2, progressIndicator2); 125 | } 126 | 127 | private void bind(Task task, Label subLabel, ProgressBar progressBar, ProgressIndicator progressIndicator) { 128 | if (task != null) { 129 | subLabel.textProperty().bind(task.titleProperty()); 130 | progressBar.progressProperty().bind(task.progressProperty()); 131 | progressIndicator.progressProperty().bind(task.progressProperty()); 132 | } else { 133 | subLabel.textProperty().unbind(); 134 | subLabel.setText(""); 135 | progressBar.progressProperty().unbind(); 136 | progressBar.setProgress(0); 137 | progressIndicator.progressProperty().unbind(); 138 | progressIndicator.setProgress(0); 139 | } 140 | } 141 | 142 | public void log(String s, Object... args) { 143 | String s0 = String.format(s, args); 144 | System.out.println(s0); 145 | Platform.runLater(() -> console.appendText(s0 + "\n")); 146 | } 147 | 148 | public void start() { 149 | if (progressFile.exists()) { 150 | readManifest(); 151 | } else { 152 | downloadModpack(); 153 | } 154 | } 155 | 156 | private void downloadModpack() { 157 | log("Downloading modpack"); 158 | DownloadFileTask task = new DownloadFileTask(addonFile.getDownloadUrl(), new File(temp, addonFile.getFileName())); 159 | task.setOnSucceeded(e -> { 160 | log("Modpack downloaded successfully"); 161 | unzipModpack(); 162 | }); 163 | setTask1(task); 164 | CMPDL.EXECUTOR_SERVICE.execute(task); 165 | } 166 | 167 | public void unzipModpack() { 168 | log("Unzipping modpack"); 169 | UnzipFileTask task = new UnzipFileTask(zipFile, unzipFolder); 170 | task.setOnSucceeded(e -> { 171 | log("Modpack unzipped successfully"); 172 | readManifest(); 173 | }); 174 | setTask1(task); 175 | CMPDL.EXECUTOR_SERVICE.execute(task); 176 | } 177 | 178 | private void readManifest() { 179 | log("Reading manifest"); 180 | ReadManifestTask task = new ReadManifestTask(new File(unzipFolder, "manifest.json")); 181 | task.setOnSucceeded(e -> task.getValue().ifPresent(manifest -> { 182 | log("Manifest read successfully"); 183 | this.manifest = manifest; 184 | log("### Manifest content :"); 185 | log(manifest.toString()); 186 | downloadMods(); 187 | })); 188 | setTask1(task); 189 | CMPDL.EXECUTOR_SERVICE.execute(task); 190 | } 191 | 192 | private void downloadMods() { 193 | log("Downloading mods"); 194 | DownloadModsTask task = new DownloadModsTask(modsFolder, progressFile, manifest.getMods()); 195 | task.setOnSucceeded(e -> { 196 | log("Downloaded mods successfully"); 197 | copyOverrides(); 198 | }); 199 | setTask1(task); 200 | task.taskProperty().addListener((observable, oldValue, newValue) -> { 201 | if (newValue != null) setTask2(newValue); 202 | }); 203 | CMPDL.EXECUTOR_SERVICE.execute(task); 204 | } 205 | 206 | private void copyOverrides() { 207 | setTask2(null); 208 | log("Copying overrides"); 209 | CopyOverridesTask task = new CopyOverridesTask(new File(unzipFolder, manifest.getOverrides()), minecraft); 210 | task.setOnSucceeded(e -> { 211 | log("Copied overrides successfully"); 212 | clean(); 213 | }); 214 | setTask1(task); 215 | CMPDL.EXECUTOR_SERVICE.execute(task); 216 | } 217 | 218 | private void clean() { 219 | log("Cleaning"); 220 | CleanTask task = new CleanTask(temp); 221 | task.setOnSucceeded(e -> finish()); 222 | setTask1(task); 223 | CMPDL.EXECUTOR_SERVICE.execute(task); 224 | } 225 | 226 | private void finish() { 227 | done = true; 228 | setTask1(null); 229 | subLabel1.setText("!!! Modpack imported successfully !!!"); 230 | subLabel2.setText(String.format("Make sure to install %s before playing.", manifest.getForge())); 231 | log("Cleaned successfully"); 232 | log("!!! Modpack imported successfully !!!"); 233 | log("Make sure to install %s before playing.", manifest.getForge()); 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/core/ContentControllerView.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.core; 2 | 3 | import com.github.franckyi.cmpdl.controller.IContentController; 4 | 5 | import java.io.IOException; 6 | 7 | public class ContentControllerView extends ControllerView { 8 | 9 | public ContentControllerView(String res) throws IOException { 10 | super(res); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/core/ControllerView.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.core; 2 | 3 | import javafx.fxml.FXMLLoader; 4 | import javafx.scene.Parent; 5 | 6 | import java.io.IOException; 7 | 8 | public class ControllerView { 9 | 10 | private final Parent view; 11 | private final T controller; 12 | 13 | public ControllerView(String res) throws IOException { 14 | FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(res)); 15 | view = loader.load(); 16 | controller = loader.getController(); 17 | } 18 | 19 | public Parent getView() { 20 | return view; 21 | } 22 | 23 | public T getController() { 24 | return controller; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/model/ModpackManifest.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.model; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class ModpackManifest { 11 | 12 | private final String name; 13 | private final String author; 14 | private final String version; 15 | private final String mcVersion; 16 | private final String forge; 17 | private final List mods; 18 | private final String overrides; 19 | 20 | public ModpackManifest(JSONObject json) { 21 | name = json.getString("name"); 22 | author = json.getString("author"); 23 | version = json.getString("version"); 24 | mcVersion = json.getJSONObject("minecraft").getString("version"); 25 | forge = json.getJSONObject("minecraft").getJSONArray("modLoaders").getJSONObject(0).getString("id"); 26 | JSONArray array = json.getJSONArray("files"); 27 | mods = new ArrayList<>(array.length()); 28 | for (int i = 0; i < array.length(); i++) { 29 | mods.add(new ModpackManifestMod(array.getJSONObject(i))); 30 | } 31 | overrides = json.getString("overrides"); 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public String getAuthor() { 39 | return author; 40 | } 41 | 42 | public String getVersion() { 43 | return version; 44 | } 45 | 46 | public String getMcVersion() { 47 | return mcVersion; 48 | } 49 | 50 | public String getForge() { 51 | return forge; 52 | } 53 | 54 | public List getMods() { 55 | return mods; 56 | } 57 | 58 | public String getOverrides() { 59 | return overrides; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return String.format("### %s v%s by %s for MC %s\n### Mod count : %d\n### Forge version required : %s", 65 | getName().replaceAll("%", ""), getVersion().replaceAll("%", ""), 66 | getAuthor().replaceAll("%", ""), getMcVersion(), mods.size(), getForge()); 67 | } 68 | 69 | public static class ModpackManifestMod { 70 | 71 | private final int projectId, fileId; 72 | 73 | public ModpackManifestMod(JSONObject json) { 74 | projectId = json.getInt("projectID"); 75 | fileId = json.getInt("fileID"); 76 | } 77 | 78 | public ModpackManifestMod(String line) { 79 | String[] split = line.split(":"); 80 | projectId = Integer.parseInt(split[0]); 81 | fileId = Integer.parseInt(split[1]); 82 | } 83 | 84 | public int getProjectId() { 85 | return projectId; 86 | } 87 | 88 | public int getFileId() { 89 | return fileId; 90 | } 91 | 92 | @Override 93 | public boolean equals(Object o) { 94 | if (this == o) return true; 95 | if (o == null || getClass() != o.getClass()) return false; 96 | ModpackManifestMod that = (ModpackManifestMod) o; 97 | return projectId == that.projectId && 98 | fileId == that.fileId; 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | return Objects.hash(projectId, fileId); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/TaskBase.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task; 2 | 3 | import javafx.application.Platform; 4 | import javafx.concurrent.Task; 5 | import javafx.scene.control.Alert; 6 | 7 | import java.util.Optional; 8 | 9 | public abstract class TaskBase extends Task> { 10 | 11 | @Override 12 | protected Optional call() { 13 | try { 14 | return Optional.ofNullable(call0()); 15 | } catch (Throwable t) { 16 | t.printStackTrace(); 17 | Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, t.getMessage()).show()); 18 | return Optional.empty(); 19 | } 20 | } 21 | 22 | protected abstract V call0() throws Throwable; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/api/CallTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.api; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | import retrofit2.Call; 5 | 6 | import java.io.IOException; 7 | 8 | public class CallTask extends TaskBase { 9 | 10 | private final String title; 11 | private final Call call; 12 | 13 | public CallTask(String title, Call call) { 14 | this.title = title; 15 | this.call = call; 16 | } 17 | 18 | @Override 19 | protected T call0() throws IOException { 20 | updateTitle(title); 21 | return call.execute().body(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/api/GetProjectIdTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.api; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | import javafx.application.Platform; 5 | import javafx.scene.control.Alert; 6 | import javafx.scene.control.ButtonType; 7 | import org.jsoup.Jsoup; 8 | import org.jsoup.nodes.Document; 9 | 10 | import java.io.IOException; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | 14 | public class GetProjectIdTask extends TaskBase { 15 | 16 | private final String projectUrl; 17 | 18 | public GetProjectIdTask(String projectUrl) { 19 | this.projectUrl = projectUrl; 20 | } 21 | 22 | @Override 23 | protected Integer call0() { 24 | updateTitle(String.format("Getting project ID for %s", projectUrl)); 25 | try { 26 | URL url = new URL(projectUrl); 27 | Document doc = Jsoup.parse(url, 10000); 28 | if (url.getHost().equals("www.curseforge.com")) { 29 | return Integer.parseInt(doc.select("div.mb-3:nth-child(2) > div:nth-child(1) > span:nth-child(2)").html()); 30 | } else { 31 | return Integer.parseInt(doc.select(".fa-icon-twitch").attr("data-id")); 32 | } 33 | } catch (MalformedURLException e) { 34 | e.printStackTrace(); 35 | Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Malformed URL", ButtonType.OK).show()); 36 | return null; 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "Connection error", ButtonType.OK).show()); 40 | return null; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/mpimport/CopyOverridesTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.mpimport; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.CopyOption; 8 | import java.nio.file.Files; 9 | import java.nio.file.StandardCopyOption; 10 | 11 | public class CopyOverridesTask extends TaskBase { 12 | 13 | private final File srcFolder, dstFolder; 14 | 15 | public CopyOverridesTask(File srcFolder, File dstFolder) { 16 | this.srcFolder = srcFolder; 17 | this.dstFolder = dstFolder; 18 | } 19 | 20 | 21 | @Override 22 | protected Void call0() throws IOException { 23 | updateTitle("Copying overrides"); 24 | copyFolder(srcFolder, dstFolder, StandardCopyOption.REPLACE_EXISTING); 25 | return null; 26 | } 27 | 28 | private static void copyFolder(File source, File dest, CopyOption... options) throws IOException { 29 | if (!dest.exists()) 30 | dest.mkdirs(); 31 | File[] contents = source.listFiles(); 32 | if (contents != null) { 33 | for (File f : contents) { 34 | File newFile = new File(dest.getAbsolutePath() + File.separator + f.getName()); 35 | if (f.isDirectory()) 36 | copyFolder(f, newFile, options); 37 | else 38 | copyFile(f, newFile, options); 39 | } 40 | } 41 | } 42 | 43 | private static void copyFile(File source, File dest, CopyOption... options) throws IOException { 44 | Files.copy(source.toPath(), dest.toPath(), options); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadFileTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.mpimport; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.net.HttpURLConnection; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.net.URL; 13 | 14 | public class DownloadFileTask extends TaskBase { 15 | 16 | private final String src; 17 | private final File dst; 18 | 19 | public DownloadFileTask(String src, File dst) { 20 | this.src = src; 21 | this.dst = dst; 22 | } 23 | 24 | @Override 25 | protected Void call0() throws IOException, URISyntaxException { 26 | updateTitle(String.format("Downloading %s", dst.getName())); 27 | URL url = new URL(src); 28 | URI uri = new URI(url.getProtocol(), url.getHost(), url.getFile(), null); 29 | if (!dst.exists()) dst.createNewFile(); 30 | HttpURLConnection connection = (HttpURLConnection) new URL(uri.toASCIIString()).openConnection(); 31 | int size = connection.getContentLength(); 32 | BufferedInputStream bis = new BufferedInputStream(connection.getInputStream()); 33 | FileOutputStream fis = new FileOutputStream(dst); 34 | byte[] buffer = new byte[1024]; 35 | long dl = 0; 36 | int count; 37 | while ((count = bis.read(buffer, 0, 1024)) != -1 && !isCancelled()) { 38 | fis.write(buffer, 0, count); 39 | dl += count; 40 | this.updateProgress(dl, size); 41 | } 42 | fis.close(); 43 | bis.close(); 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/mpimport/DownloadModsTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.mpimport; 2 | 3 | import com.github.franckyi.cmpdl.CMPDL; 4 | import com.github.franckyi.cmpdl.api.response.AddonFile; 5 | import com.github.franckyi.cmpdl.model.ModpackManifest; 6 | import com.github.franckyi.cmpdl.task.TaskBase; 7 | import javafx.application.Platform; 8 | import javafx.beans.property.ObjectProperty; 9 | import javafx.beans.property.ReadOnlyObjectProperty; 10 | import javafx.beans.property.SimpleObjectProperty; 11 | import javafx.concurrent.Task; 12 | 13 | import java.io.*; 14 | import java.util.List; 15 | 16 | public class DownloadModsTask extends TaskBase { 17 | 18 | private final File modsFolder, progressFile; 19 | private final List mods; 20 | 21 | private final ObjectProperty> task = new SimpleObjectProperty<>(); 22 | 23 | public DownloadModsTask(File modsFolder, File progressFile, List mods) { 24 | this.modsFolder = modsFolder; 25 | this.progressFile = progressFile; 26 | this.mods = mods; 27 | } 28 | 29 | @Override 30 | protected Void call0() throws IOException { 31 | int max = mods.size(); 32 | int start = 0; 33 | if (progressFile.exists() && progressFile.isFile()) { 34 | BufferedReader reader = new BufferedReader(new FileReader(progressFile)); 35 | String line; 36 | while ((line = reader.readLine()) != null && !isCancelled()) { 37 | ModpackManifest.ModpackManifestMod mod = new ModpackManifest.ModpackManifestMod(line); 38 | mods.remove(mod); 39 | CMPDL.progressPane.getController().log("File %d:%d already downloaded - skipping", mod.getProjectId(), mod.getFileId()); 40 | start++; 41 | } 42 | reader.close(); 43 | } else { 44 | progressFile.createNewFile(); 45 | } 46 | try (BufferedWriter writer = new BufferedWriter(new FileWriter(progressFile, true))) { 47 | for (int i = start; i < max; i++) { 48 | updateProgress(i, max); 49 | if (isCancelled()) { 50 | writer.close(); 51 | return null; 52 | } 53 | updateTitle(String.format("Downloading mods (%d/%d)", i + 1, max)); 54 | ModpackManifest.ModpackManifestMod mod = mods.get(i - start); 55 | CMPDL.progressPane.getController().log("Resolving file %d:%d", mod.getProjectId(), mod.getFileId()); 56 | AddonFile file = CMPDL.getAPI().getFile(mod.getProjectId(), mod.getFileId()).execute().body(); 57 | if (file != null) { 58 | DownloadFileTask task = new DownloadFileTask(file.getDownloadUrl(), new File(modsFolder, file.getFileName())); 59 | setTask(task); 60 | CMPDL.progressPane.getController().log("Downloading file %s", file.getFileName().replaceAll("%", "")); 61 | task.setOnSucceeded(e -> { 62 | try { 63 | writer.write(String.format("%d:%d\n", mod.getProjectId(), mod.getFileId())); 64 | } catch (IOException e1) { 65 | e1.printStackTrace(); 66 | } 67 | }); 68 | task.run(); 69 | } else { 70 | CMPDL.progressPane.getController().log("!!! Unknown file %d:%d - skipping !!!", mod.getProjectId(), mod.getFileId()); 71 | } 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | 78 | private void setTask(Task task) { 79 | Platform.runLater(() -> this.task.setValue(task)); 80 | } 81 | 82 | public ReadOnlyObjectProperty> taskProperty() { 83 | return task; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/mpimport/ReadManifestTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.mpimport; 2 | 3 | import com.github.franckyi.cmpdl.model.ModpackManifest; 4 | import com.github.franckyi.cmpdl.task.TaskBase; 5 | import org.json.JSONObject; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.charset.StandardCharsets; 10 | import java.nio.file.Files; 11 | 12 | public class ReadManifestTask extends TaskBase { 13 | 14 | private final File manifestFile; 15 | 16 | public ReadManifestTask(File manifestFile) { 17 | this.manifestFile = manifestFile; 18 | } 19 | 20 | @Override 21 | protected ModpackManifest call0() throws IOException { 22 | return new ModpackManifest(new JSONObject(new String(Files.readAllBytes(manifestFile.toPath()), StandardCharsets.UTF_8))); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/task/mpimport/UnzipFileTask.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.task.mpimport; 2 | 3 | import com.github.franckyi.cmpdl.task.TaskBase; 4 | 5 | import java.io.*; 6 | import java.nio.channels.FileChannel; 7 | import java.util.zip.ZipEntry; 8 | import java.util.zip.ZipInputStream; 9 | 10 | public class UnzipFileTask extends TaskBase { 11 | 12 | private final File src, dst; 13 | 14 | public UnzipFileTask(File src, File dst) { 15 | this.src = src; 16 | this.dst = dst; 17 | } 18 | 19 | @Override 20 | protected Void call0() throws Throwable { 21 | updateTitle(String.format("Unzipping %s", src.getName())); 22 | FileInputStream is = new FileInputStream(src.getCanonicalFile()); 23 | FileChannel channel = is.getChannel(); 24 | ZipEntry ze; 25 | try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is))) { 26 | while ((ze = zis.getNextEntry()) != null && !isCancelled()) { 27 | File f = new File(dst.getCanonicalPath(), ze.getName()); 28 | if (ze.isDirectory()) { 29 | f.mkdirs(); 30 | continue; 31 | } 32 | f.getParentFile().mkdirs(); 33 | try { 34 | try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) { 35 | final byte[] buf = new byte[1024]; 36 | int bytesRead; 37 | long nread = 0L; 38 | long length = src.length(); 39 | while (-1 != (bytesRead = zis.read(buf)) && !isCancelled()) { 40 | fos.write(buf, 0, bytesRead); 41 | nread += bytesRead; 42 | updateProgress(channel.position(), length); 43 | } 44 | } 45 | } catch (final IOException ioe) { 46 | f.delete(); 47 | throw ioe; 48 | } 49 | } 50 | } 51 | updateProgress(0, 0); 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/github/franckyi/cmpdl/view/AddonFileMinimalView.java: -------------------------------------------------------------------------------- 1 | package com.github.franckyi.cmpdl.view; 2 | 3 | import com.github.franckyi.cmpdl.api.response.AddonFile; 4 | import javafx.geometry.Insets; 5 | import javafx.scene.control.Label; 6 | import javafx.scene.control.ListCell; 7 | import javafx.scene.layout.Background; 8 | import javafx.scene.layout.BackgroundFill; 9 | import javafx.scene.layout.CornerRadii; 10 | import javafx.scene.layout.VBox; 11 | import javafx.scene.paint.Color; 12 | import javafx.scene.text.Font; 13 | import javafx.scene.text.FontWeight; 14 | 15 | public class AddonFileMinimalView extends ListCell { 16 | 17 | private final VBox root = new VBox(5); 18 | private final Label fileName = new Label(); 19 | private final Label gameVersion = new Label(); 20 | private final Label fileType = new Label(); 21 | 22 | public AddonFileMinimalView() { 23 | fileName.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, 16)); 24 | fileType.setFont(Font.font(Font.getDefault().getFamily(), FontWeight.BOLD, Font.getDefault().getSize())); 25 | root.getChildren().addAll(fileName, gameVersion, fileType); 26 | VBox.setMargin(fileType, new Insets(0, 0, 0, 2)); 27 | } 28 | 29 | @Override 30 | protected void updateItem(AddonFile item, boolean empty) { 31 | super.updateItem(item, empty); 32 | if (empty) { 33 | setGraphic(null); 34 | } else { 35 | fileName.setText(item.getDisplayName()); 36 | gameVersion.setText("for MC " + String.join(", ", item.getGameVersion())); 37 | fileType.setText(item.getReleaseType().toString()); 38 | fileType.setBackground(new Background(new BackgroundFill(item.getReleaseType().getColor(), new CornerRadii(5), new Insets(-2, -5, -2, -5)))); 39 | fileType.setTextFill(Color.WHITE); 40 | setGraphic(root); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/resources/fxml/DestinationPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 32 | 36 | 37 | 39 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 67 | 68 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/resources/fxml/ModpackPane.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | 32 | 33 | 34 | 35 | 37 | 39 | 40 | 41 |