Skip to content

Commit f21a8e9

Browse files
committed
Made the config dialog better
1 parent 2363711 commit f21a8e9

12 files changed

+758
-524
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ loader_version=0.16.14
1010
loom_version=1.10-SNAPSHOT
1111

1212
# Mod Properties
13-
mod_version=2.1.2-1.21.5-fabric
13+
mod_version=2.2.0-1.21.5-fabric
1414
maven_group=com.kd_gaming1
1515
archives_base_name=packcore
1616

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.kd_gaming1.copysystem;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.File;
7+
import java.io.IOException;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import java.util.function.Consumer;
13+
14+
/**
15+
* Service class that handles the business logic for config detection and extraction.
16+
* Separates the UI concerns from the actual extraction logic.
17+
*/
18+
public class ConfigExtractionService {
19+
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigExtractionService.class);
20+
21+
private final File minecraftRoot;
22+
private final File skyblockFolder;
23+
private final ConfigExtractor extractor;
24+
25+
public ConfigExtractionService(File minecraftRoot) {
26+
this.minecraftRoot = minecraftRoot;
27+
this.skyblockFolder = new File(minecraftRoot, "Skyblock Enhanced");
28+
this.extractor = new ConfigExtractor();
29+
}
30+
31+
/**
32+
* Analyzes available configs and determines what action to take
33+
*/
34+
public ConfigSelectionResult selectAndExtractConfig() {
35+
List<ConfigInfo> officialConfigs = scanForConfigs("OfficialConfigs");
36+
List<ConfigInfo> customConfigs = scanForConfigs("CustomConfigs");
37+
38+
// If custom configs exist, always show dialog for user choice
39+
if (!customConfigs.isEmpty()) {
40+
return ConfigSelectionResult.showDialog(officialConfigs, customConfigs);
41+
}
42+
43+
// No custom configs, check official configs
44+
if (officialConfigs.isEmpty()) {
45+
return ConfigSelectionResult.noConfigs();
46+
} else if (officialConfigs.size() == 1) {
47+
ConfigInfo singleConfig = officialConfigs.get(0);
48+
return ConfigSelectionResult.autoExtract(singleConfig.getName(), ConfigType.OFFICIAL);
49+
} else {
50+
return ConfigSelectionResult.showDialog(officialConfigs, customConfigs);
51+
}
52+
}
53+
54+
/**
55+
* Scans a specific folder for config files
56+
*/
57+
private List<ConfigInfo> scanForConfigs(String folderName) {
58+
List<ConfigInfo> configs = new ArrayList<>();
59+
File folder = new File(skyblockFolder, folderName);
60+
61+
if (!folder.exists() || !folder.isDirectory()) {
62+
return configs;
63+
}
64+
65+
File[] files = folder.listFiles();
66+
if (files != null) {
67+
for (File file : files) {
68+
if (file.isFile() && file.getName().toLowerCase().endsWith(".zip")) {
69+
configs.add(new ConfigInfo(file.getName(), file.length()));
70+
}
71+
}
72+
}
73+
74+
return configs;
75+
}
76+
77+
/**
78+
* Extracts a specific config file
79+
*/
80+
public boolean extractConfig(String configName, ConfigType configType) {
81+
return extractConfig(configName, configType, progress -> {
82+
LOGGER.debug("Extraction progress: {}%", progress);
83+
});
84+
}
85+
86+
/**
87+
* Extracts a specific config file with progress callback
88+
*/
89+
public boolean extractConfig(String configName, ConfigType configType, Consumer<Integer> progressCallback) {
90+
String subfolderName = configType == ConfigType.OFFICIAL ? "OfficialConfigs" : "CustomConfigs";
91+
File configFile = new File(new File(skyblockFolder, subfolderName), configName);
92+
93+
if (!configFile.exists()) {
94+
LOGGER.error("Config file not found: {}", configFile.getAbsolutePath());
95+
return false;
96+
}
97+
98+
try {
99+
return extractor.extractZipToDirectory(configFile, minecraftRoot, progressCallback);
100+
} catch (IOException e) {
101+
LOGGER.error("Failed to extract config: {}", configName, e);
102+
return false;
103+
}
104+
}
105+
106+
public List<ConfigInfo> getOfficialConfigs() {
107+
return scanForConfigs("OfficialConfigs");
108+
}
109+
110+
public List<ConfigInfo> getCustomConfigs() {
111+
return scanForConfigs("CustomConfigs");
112+
}
113+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package com.kd_gaming1.copysystem;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.File;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.StandardCopyOption;
12+
import java.util.ArrayList;
13+
import java.util.Enumeration;
14+
import java.util.List;
15+
import java.util.function.Consumer;
16+
import java.util.zip.ZipEntry;
17+
import java.util.zip.ZipException;
18+
import java.util.zip.ZipFile;
19+
20+
/**
21+
* Handles the actual extraction of ZIP files with proper error handling and progress reporting.
22+
*/
23+
public class ConfigExtractor {
24+
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigExtractor.class);
25+
26+
/**
27+
* Extracts a ZIP file to the target directory with progress reporting
28+
*/
29+
public boolean extractZipToDirectory(File zipFile, File targetDirectory, Consumer<Integer> progressCallback)
30+
throws IOException {
31+
32+
if (!zipFile.exists()) {
33+
throw new IOException("ZIP file does not exist: " + zipFile.getAbsolutePath());
34+
}
35+
36+
if (!targetDirectory.exists() && !targetDirectory.mkdirs()) {
37+
throw new IOException("Could not create target directory: " + targetDirectory.getAbsolutePath());
38+
}
39+
40+
try (ZipFile zip = new ZipFile(zipFile)) {
41+
List<ZipEntry> entries = collectEntries(zip);
42+
43+
if (entries.isEmpty()) {
44+
LOGGER.warn("ZIP file is empty: {}", zipFile.getName());
45+
return true;
46+
}
47+
48+
return extractEntries(zip, entries, targetDirectory, progressCallback);
49+
50+
} catch (ZipException e) {
51+
throw new IOException("Invalid ZIP file: " + zipFile.getName(), e);
52+
}
53+
}
54+
55+
private List<ZipEntry> collectEntries(ZipFile zipFile) {
56+
List<ZipEntry> entries = new ArrayList<>();
57+
Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
58+
59+
while (enumeration.hasMoreElements()) {
60+
ZipEntry entry = enumeration.nextElement();
61+
// Skip directory entries, we'll create them as needed
62+
if (!entry.isDirectory()) {
63+
entries.add(entry);
64+
}
65+
}
66+
67+
return entries;
68+
}
69+
70+
private boolean extractEntries(ZipFile zipFile, List<ZipEntry> entries, File targetDirectory,
71+
Consumer<Integer> progressCallback) throws IOException {
72+
73+
int totalEntries = entries.size();
74+
int processedEntries = 0;
75+
76+
for (ZipEntry entry : entries) {
77+
try {
78+
extractSingleEntry(zipFile, entry, targetDirectory);
79+
processedEntries++;
80+
81+
// Report progress
82+
int progress = (int) ((double) processedEntries / totalEntries * 100);
83+
progressCallback.accept(progress);
84+
85+
} catch (IOException e) {
86+
LOGGER.error("Failed to extract entry: {}", entry.getName(), e);
87+
throw e;
88+
}
89+
}
90+
91+
LOGGER.info("Successfully extracted {} entries from ZIP file", processedEntries);
92+
return true;
93+
}
94+
95+
private void extractSingleEntry(ZipFile zipFile, ZipEntry entry, File targetDirectory) throws IOException {
96+
// Validate entry name to prevent directory traversal attacks
97+
String entryName = validateEntryName(entry.getName());
98+
99+
Path targetPath = new File(targetDirectory, entryName).toPath();
100+
101+
// Create parent directories if they don't exist
102+
Path parentPath = targetPath.getParent();
103+
if (parentPath != null && !Files.exists(parentPath)) {
104+
Files.createDirectories(parentPath);
105+
}
106+
107+
// Extract the file
108+
try (InputStream inputStream = zipFile.getInputStream(entry)) {
109+
Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
110+
}
111+
112+
LOGGER.debug("Extracted: {}", entryName);
113+
}
114+
115+
private String validateEntryName(String entryName) throws IOException {
116+
// Normalize the entry name and check for directory traversal
117+
String normalizedName = entryName.replace('\\', '/');
118+
119+
if (normalizedName.contains("../") || normalizedName.startsWith("/")) {
120+
throw new IOException("Invalid entry name (potential directory traversal): " + entryName);
121+
}
122+
123+
return normalizedName;
124+
}
125+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.kd_gaming1.copysystem;
2+
3+
/**
4+
* Data class representing information about a configuration file
5+
*/
6+
public class ConfigInfo {
7+
private final String name;
8+
private final long size;
9+
10+
public ConfigInfo(String name, long size) {
11+
this.name = name;
12+
this.size = size;
13+
}
14+
15+
public String getName() {
16+
return name;
17+
}
18+
19+
public long getSize() {
20+
return size;
21+
}
22+
23+
public String getDisplayName() {
24+
// Remove .zip extension for display
25+
if (name.toLowerCase().endsWith(".zip")) {
26+
return name.substring(0, name.length() - 4);
27+
}
28+
return name;
29+
}
30+
31+
@Override
32+
public String toString() {
33+
return getDisplayName();
34+
}
35+
36+
@Override
37+
public boolean equals(Object obj) {
38+
if (this == obj) return true;
39+
if (obj == null || getClass() != obj.getClass()) return false;
40+
ConfigInfo that = (ConfigInfo) obj;
41+
return name.equals(that.name);
42+
}
43+
44+
@Override
45+
public int hashCode() {
46+
return name.hashCode();
47+
}
48+
}

0 commit comments

Comments
 (0)