196 lines
7.7 KiB
Java
196 lines
7.7 KiB
Java
package io.gitlab.jfronny.resclone;
|
|
|
|
import io.gitlab.jfronny.commons.log.Logger;
|
|
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders;
|
|
import io.gitlab.jfronny.resclone.api.*;
|
|
import io.gitlab.jfronny.resclone.data.PackMetaLoaded;
|
|
import io.gitlab.jfronny.resclone.data.PackMetaUnloaded;
|
|
import io.gitlab.jfronny.resclone.processors.RemoveEmptyProcessor;
|
|
import io.gitlab.jfronny.resclone.processors.RootPathProcessor;
|
|
import io.gitlab.jfronny.resclone.util.PackUrlCache;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.ModInitializer;
|
|
import net.fabricmc.loader.api.FabricLoader;
|
|
|
|
import java.io.IOException;
|
|
import java.net.URI;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
public class Resclone implements ModInitializer, RescloneApi {
|
|
public static final Set<PackMetaUnloaded> CONF = new LinkedHashSet<>();
|
|
public static final Map<String, PackFetcher> FETCHER_INSTANCES = new LinkedHashMap<>();
|
|
public static final Set<PackProcessor> PROCESSORS = new LinkedHashSet<>();
|
|
public static final Set<PackMetaLoaded> DOWNLOADED_PACKS = new LinkedHashSet<>();
|
|
public static final Set<PackMetaLoaded> NEW_PACKS = new LinkedHashSet<>(); // Client-only!
|
|
|
|
public static final String MOD_ID = "resclone";
|
|
public static final Logger LOGGER = Logger.forName(MOD_ID);
|
|
public static final String USER_AGENT = "jfmods/" + MOD_ID + "/" + FabricLoader.getInstance()
|
|
.getModContainer(MOD_ID).orElseThrow()
|
|
.getMetadata()
|
|
.getVersion()
|
|
.getFriendlyString();
|
|
|
|
public static PackUrlCache urlCache;
|
|
|
|
public static int packCount = 0;
|
|
private boolean pruneUnused = false;
|
|
|
|
@Override
|
|
public void onInitialize() {
|
|
LOGGER.info("Initialising Resclone.");
|
|
GsonHolders.registerSerializer();
|
|
|
|
urlCache = new PackUrlCache(getConfigPath().resolve("urlCache.properties"));
|
|
CONF.clear();
|
|
FETCHER_INSTANCES.clear();
|
|
PROCESSORS.clear();
|
|
DOWNLOADED_PACKS.clear();
|
|
|
|
addProcessor(new RootPathProcessor()); //This should be run before any other processor to make sure the path is valid
|
|
for (RescloneEntry entry : FabricLoader.getInstance().getEntrypoints(MOD_ID, RescloneEntry.class)) {
|
|
try {
|
|
entry.init(this);
|
|
} catch (Exception e) {
|
|
Resclone.LOGGER.error("Could not initialize resclone pack supplier", e);
|
|
}
|
|
}
|
|
addProcessor(new RemoveEmptyProcessor());
|
|
reload();
|
|
|
|
LOGGER.info("Installed {} resource pack{}.", packCount, packCount == 1 ? "" : "s");
|
|
}
|
|
|
|
@Override
|
|
public void addFetcher(PackFetcher fetcher) {
|
|
FETCHER_INSTANCES.put(fetcher.getSourceTypeName(), fetcher);
|
|
}
|
|
|
|
@Override
|
|
public void addProcessor(PackProcessor processor) {
|
|
PROCESSORS.add(processor);
|
|
}
|
|
|
|
@Override
|
|
public void addPack(String fetcher, String pack, String name) {
|
|
addPack(fetcher, pack, name, false);
|
|
}
|
|
|
|
@Override
|
|
public void addPack(String fetcher, String pack, String name, boolean forceRedownload) {
|
|
addPack(fetcher, pack, name, forceRedownload, false);
|
|
}
|
|
|
|
@Override
|
|
public void addPack(String fetcher, String pack, String name, boolean forceRedownload, boolean forceEnable) {
|
|
CONF.add(new PackMetaUnloaded(fetcher, pack, name, forceRedownload, forceEnable));
|
|
}
|
|
|
|
@Override
|
|
public void reload() {
|
|
Set<PackMetaLoaded> metas = new LinkedHashSet<>();
|
|
try {
|
|
if (CONF.isEmpty()) {
|
|
LOGGER.info("No resclone pack was specified, add one");
|
|
}
|
|
else {
|
|
ExecutorService pool = Executors.newFixedThreadPool(CONF.size());
|
|
for (PackMetaUnloaded s : CONF) {
|
|
pool.submit(generateTask(s, metas));
|
|
}
|
|
pool.shutdown();
|
|
if (!pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)) {
|
|
LOGGER.error("Download timed out. This shouldn't be possible");
|
|
}
|
|
}
|
|
} catch (InterruptedException e) {
|
|
LOGGER.error("Could not execute pack download task", e);
|
|
}
|
|
urlCache.save();
|
|
DOWNLOADED_PACKS.clear();
|
|
DOWNLOADED_PACKS.addAll(metas);
|
|
|
|
if (pruneUnused) pruneCache();
|
|
}
|
|
|
|
private Runnable generateTask(PackMetaUnloaded meta, Set<PackMetaLoaded> metas) {
|
|
return () -> {
|
|
try {
|
|
if (!FETCHER_INSTANCES.containsKey(meta.fetcher))
|
|
throw new Exception("Invalid fetcher: " + meta.fetcher);
|
|
Path cacheDir = getConfigPath().resolve("cache");
|
|
PackMetaLoaded p;
|
|
try {
|
|
boolean isNew = !urlCache.containsKey(meta.source);
|
|
//Download
|
|
PackFetcher.Result fr = FETCHER_INSTANCES.get(meta.fetcher).get(meta.source, cacheDir, meta.forceDownload);
|
|
p = new PackMetaLoaded(fr.downloadPath(), meta.name, meta.forceEnable);
|
|
metas.add(p);
|
|
if (isNew && FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) NEW_PACKS.add(p);
|
|
if (fr.freshDownload()) {
|
|
//Process
|
|
Map<String, String> props = new HashMap<>();
|
|
props.put("create", "false");
|
|
URI zipfile = URI.create("jar:" + p.zipPath().toUri());
|
|
try (FileSystem zipfs = FileSystems.newFileSystem(zipfile, props)) {
|
|
for (PackProcessor processor : PROCESSORS) {
|
|
processor.process(zipfs);
|
|
}
|
|
} catch (Throwable e) {
|
|
LOGGER.error("Could not run pack processors on " + p.zipPath(), e);
|
|
}
|
|
}
|
|
} catch (Throwable e) {
|
|
throw new Exception("Failed to download pack", e);
|
|
}
|
|
} catch (Throwable e) {
|
|
LOGGER.error("Encountered issue while preparing " + meta.name, e);
|
|
}
|
|
};
|
|
}
|
|
|
|
private void pruneCache() {
|
|
Set<Path> loadedPacks = DOWNLOADED_PACKS.stream().map(PackMetaLoaded::zipPath).collect(Collectors.toUnmodifiableSet());
|
|
Set<Path> toDelete = new HashSet<>();
|
|
try (Stream<Path> cacheEntries = Files.list(getConfigPath().resolve("cache"))) {
|
|
cacheEntries
|
|
.filter(s -> !Files.isRegularFile(s) || !loadedPacks.contains(s))
|
|
.forEach(toDelete::add);
|
|
} catch (IOException e) {
|
|
LOGGER.error("Could find cache entries to prune", e);
|
|
}
|
|
if (!toDelete.isEmpty()) {
|
|
LOGGER.info("Pruning " + toDelete.size() + " unused cache entries");
|
|
for (Path path : toDelete) {
|
|
try {
|
|
Files.delete(path);
|
|
} catch (IOException e) {
|
|
LOGGER.error("Could not delete unused cache entry: " + path, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Path getConfigPath() {
|
|
Path configPath = FabricLoader.getInstance().getConfigDir().resolve("resclone");
|
|
if (!Files.isDirectory(configPath.resolve("cache"))) {
|
|
try {
|
|
Files.createDirectories(configPath.resolve("cache"));
|
|
} catch (IOException e) {
|
|
LOGGER.error("Could not create cache directory", e);
|
|
}
|
|
}
|
|
return configPath;
|
|
}
|
|
|
|
@Override
|
|
public void setPruneUnused(boolean pruneUnused) {
|
|
this.pruneUnused = pruneUnused;
|
|
}
|
|
}
|